pax_global_header00006660000000000000000000000064140043041450014505gustar00rootroot0000000000000052 comment=bd3c56b4ee5844e4aa09a1d8a7cf4c8c7ae2f88e apt-transport-in-toto-0.1.1/000077500000000000000000000000001400430414500157115ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/.coveragerc000066400000000000000000000003461400430414500200350ustar00rootroot00000000000000[run] # enable branch coverage in addition to statement coverage branch = True parallel = True omit = */tests/* */python?/* */python?.?/* */site-packages/* */dist-packages/* [report] exclude_lines = pragma: no cover apt-transport-in-toto-0.1.1/.github/000077500000000000000000000000001400430414500172515ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/.github/ISSUE_TEMPLATE.md000066400000000000000000000003351400430414500217570ustar00rootroot00000000000000Please fill in the fields below to submit an issue or feature request. The more information that is provided, the better. **Description of issue or feature request**: **Current behavior**: **Expected behavior**: apt-transport-in-toto-0.1.1/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000010361400430414500230520ustar00rootroot00000000000000Please fill in the fields below to submit a pull request. The more information that is provided, the better. **Fixes issue #**: **Description of the changes being introduced by the pull request**: **Please verify and check that the pull request fulfills the following requirements**: - [ ] The code follows the [Code Style Guidelines](https://github.com/secure-systems-lab/code-style-guidelines#code-style-guidelines) - [ ] Tests have been added for the bug fix or new feature - [ ] Docs have been added for the bug fix or new feature apt-transport-in-toto-0.1.1/.gitignore000066400000000000000000000025231400430414500177030ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # macOS .DS_Store # pycharm .idea apt-transport-in-toto-0.1.1/.travis.yml000066400000000000000000000010141400430414500200160ustar00rootroot00000000000000dist: bionic language: python matrix: include: # Explicitly tell Travis which Python version to use for which # tox environment, instead of using one Python version in Travis and # hoping that tox and pyenv run the tests in the desired versions. # https://github.com/travis-ci/travis-ci/issues/8363#issuecomment-355090242 - python: "3.9" env: TOXENV=py39 install: - pip install -U tox - pip install -U coveralls script: - tox after_success: coveralls apt-transport-in-toto-0.1.1/CHANGELOG.md000066400000000000000000000001731400430414500175230ustar00rootroot00000000000000# Changelog ## v0.1.1 - Update installation instructions in README.md - Add this CHANGELOG.md ## v0.1.0 Initial release. apt-transport-in-toto-0.1.1/LICENSE000066400000000000000000000010601400430414500167130ustar00rootroot00000000000000Copyright 2018 New York University Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. apt-transport-in-toto-0.1.1/README.md000066400000000000000000000103001400430414500171620ustar00rootroot00000000000000# apt-transport-in-toto [![Build Status](https://travis-ci.com/in-toto/apt-transport-in-toto.svg?branch=develop)](https://travis-ci.com/in-toto/apt-transport-in-toto) [![Coverage Status](https://coveralls.io/repos/github/in-toto/apt-transport-in-toto/badge.svg?branch=develop)](https://coveralls.io/github/in-toto/apt-transport-in-toto?branch=develop) A custom transport method for apt that verifies the reproducibility of a Debian package before its installation. Verification is performed with [*in-toto*](https://in-toto.io), using a supply chain definition (*in-toto layout*) and gathering the corresponding evidence (*in-toto links*) about the reproducibility of a package from public [*rebuilders*](https://salsa.debian.org/reproducible-builds/debian-rebuilder-setup). ### Installation The transport and its dependencies are available via `apt`. Below command installs the transport to `/usr/lib/apt/methods/intoto`, as well as a default config file and layout (see below). ``` sudo apt install apt-transport-in-toto ``` #### Layout To define the requirement of reproducibility for a package, an in-toto layout must be available on the client at verification time and its path must be specified in the apt configuration file (see [*Options*](https://github.com/in-toto/apt-transport-in-toto#options) below). A generic rebuild layout ([`data/root.layout`](data/root.layout)) is made available in `/etc/intoto/root.layout` upon installation. It contains public keys to verify the authenticity and integrity of rebuilder link metadata generated by currently available rebuilders, and a threshold that specifies how many authorized rebuilders need to agree on their result. --- **NOTE:** *Update the layout to add or revoke rebuilder authorizations. See discussion in [#13](https://github.com/in-toto/apt-transport-in-toto/issues/13) for further details.* --- #### Layout keys For a successful verification the layout requires at least one valid signature. The signing key(s) are the root of trust and must be available in a gpg keyring on the client. The corresponding keyid(s) must be specified in the apt configuration file (see [*Options*](https://github.com/in-toto/apt-transport-in-toto#options) below). --- **NOTE:** *Downstream maintainers should manually verify the validity of [`data/root.layout`](data/root.layout) and sign it with their maintainer key. See discussion in [#13](https://github.com/in-toto/apt-transport-in-toto/issues/13) for further details.* --- #### Options Below options must be configured in `/etc/apt/apt.conf.d/intoto`. - *Rebuilders* -- URIs of remote rebuilders that serve in-toto link metadata for package rebuilds - *in-toto layout* -- Path to supply chain definition - *Layout keyids* -- Keyid(s) of in-toto layout signing key(s) - *GPGHomedir (optional)* -- Path to a non-default gpg keyring - *LogLevel (optional)* -- Transport verbosity level during installation ([numeric value](https://docs.python.org/3/library/logging.html#logging-levels)) - *NoFail (optional)* -- If set to "true" installation continues after a verification failure, but only if the failure reason is missing link metadata. This option may be used for a slow roll-out. It should be disabled once there is broad network of rebuilders that provide extensive link metadata. An exemplary configuration file can be found in [`apt.conf.d/intoto`](apt.conf.d/intoto). #### Enable the transport Verification is enabled by specifying the transport method as protocol prefix `"intoto"` in `/etc/apt/sources.list` or `/etc/apt/sources.list.d/*`, e.g.: ``` deb intoto://ftp.us.debian.org/debian/ stretch main contrib ``` ### Usage The in-toto apt transport works transparently in the background when running: ``` apt-get install ``` ### Testing The test suite can be run locally with `tox`. #### Testing with docker In addition to the offline Python tests that mock `apt` and `rebuilder` behavior, there is a docker setup that installs the apt transport in a minimal Debian container and invokes it using `apt-get install `, fetching metadata from live rebuilders. Run the following snippet in the root of this repo and look at the generated output. ```shell docker build -t apt -f tests/Dockerfile . docker run -it apt ```apt-transport-in-toto-0.1.1/apt.conf.d/000077500000000000000000000000001400430414500176435ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/apt.conf.d/intoto000066400000000000000000000012171400430414500211030ustar00rootroot00000000000000APT::Intoto { LogLevel {"20"}; # See https://docs.python.org/3/library/logging.html#logging-levels Rebuilders { "http://158.39.77.214"; "https://reproducible-builds.engineering.nyu.edu"; }; GPGHomedir {"/path/to/gpg/keyring"}; Layout {"/path/to/root.layout"}; Keyids { "88876A89E3D4698F83D3DB0E72E33CA3E0E04E46" }; // If set to "true" apt installation will continue even if in-toto // verification fails, but only if it fails because of missing link // metadata. // This is meant for a slow rollout, as soon as there is broad support for // rebuilt debian packages, this option should not be used. NoFail {"true"} }; apt-transport-in-toto-0.1.1/data/000077500000000000000000000000001400430414500166225ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/data/intoto.conf000066400000000000000000000004021400430414500210010ustar00rootroot00000000000000APT::Intoto { LogLevel {"20"}; Rebuilders { "http://158.39.77.214"; "https://reproducible-builds.engineering.nyu.edu"; }; Layout {"/etc/intoto/root.layout"}; Keyids { "88876A89E3D4698F83D3DB0E72E33CA3E0E04E46" }; NoFail {"true"} }; apt-transport-in-toto-0.1.1/data/root.layout000066400000000000000000000067121400430414500210520ustar00rootroot00000000000000{ "signatures": [ ], "signed": { "_type": "layout", "expires": "2021-01-06T18:30:57Z", "inspect": [ { "_type": "inspection", "expected_materials": [ [ "MATCH", "*.deb", "WITH", "PRODUCTS", "FROM", "rebuild" ], [ "DISALLOW", "*.deb" ] ], "expected_products": [], "name": "verify-reprobuilds", "run": [ "/usr/bin/true" ] } ], "keys": { "2e7be98291270e3b7fca429a2210e99cff22017e": { "hashes": [ "pgp+SHA2" ], "keyid": "2e7be98291270e3b7fca429a2210e99cff22017e", "keyval": { "private": "", "public": { "e": "010001", "n": "e0da84becb294c355f9d586cb9c14e4e7707db0ccd301d41b4926d34602a35e62f26b5c092c7bb48b8c196e2506c45882b3098788f81663b079eadc61e2a40b7059032c9865059e967d7fa01a816849c646f8d9d5b7f7c0a57920bb05e2aec8e5c7116a09f693d4ed39c13fe7f53191035f4265d1f3b68e37987da5c300aa03b987b86a9d3d7e10e48a67b5631386e10b2d2832a984ddb3706d672c49575c78f8d3d1ce0a195466feb7604a2e04a28b1aa44879c812b180c453cd1d5494e48fde42cc3970d0267a39e41ba4e5e116812e3ade8dcc5e6875cb1df12349f9936d849d6dd3e11ca1067ab70c0dfd0a3770c49d239fa7fdb2a5d47963578deb5c8a6ab1460d986d9bef4ea42b90913b35d7b121bc83ef21f6872ea5bb898fdaa5ccd028a2c7ea5c89c30202b035a7bd5eededca1475a77c565092d8629d1250a9d658373fd9026b2bb72662835fb09bcf73c4256931435f72040e771f3ecaab3b3056ffe699290385211cf276528b5867e868a5df5ec1e5631313b3145de9faed46544653f9073ec55c2da962e6fbc8f9f603348e3d8b55eec078af83b2e6d0d15adacbb4bf212a3e72c806322e84255c85ea3e33d1702942833837afdf71f0068c3bdf9a2b6c3ab3bae309b13466a05ebad14c1cd37c993af0d2a34f42ba10c3630cf2da6a0804186bc2cfd2e4be1995c631527fc61e28bdf7a62e9f3f3f5e5f27f" } }, "method": "pgp+rsa-pkcsv1.5", "type": "rsa" }, "918b19596d24161290d531addc4a0582b3590165": { "hashes": [ "pgp+SHA2" ], "keyid": "918b19596d24161290d531addc4a0582b3590165", "keyval": { "private": "", "public": { "e": "010001", "n": "c12e8775178aef5249f654de9a0168a6790ca6fbf7540d8209e70330542085132d5df6c3ec7753d90dc7fd63758ae91e3cd0abb03f24c57aabd35adfc6a2161e4cf5cc59c68a7b80dd4784fa78c2c4ce19c22e298f818c429537d57b9f000c2b7febe6985a5da6436bf6a8e195eb5f082fc73bbe3e639b5be826d727664c6e0d3801109a526c5215996cd7d80ed79db4308ab732f813d5f9ab2afb3e6a66c4bd3c6b5481c87f98ca206006e5fbed85edb3a63710459007e3e234b2cf4412eb46dbadf7c5859d93c35d95a50a487b759714359026ee74b30c6df500dc23bd6cc13aedecafe915389a4f563d7150a0771bfed91d96117225d68ae23911099442576e800c3d02393be6d0c1aef0ae8cc00675f64a23e9e418348b73bc9c992ce0ffe5d14385346381cbbcaad1978c740b4f0c33165989ac232ddc23a3fec4d8d75484bfc4867716e86d365e08b21b069a4bf3a06bb86066ed45ca417a42766e4ecb0cd6a21e7f2ff2aed14cc9728f6959fa7c6bd0560fc36947a5ce7d60f90ae2eb1e8890e63f600f36aed345002fed0a59ec8531a16ce803caaf77caf466e089bc606068cdefe931fd5b5353c75f4aa540eafc4464aaec94efee7fb24d3c7b9c8db6024d2527accfb4fa79eff61082011fa48aa5c7b5cab022328cfcde25f341b231537351c18bdb82dbf36c74ec6af50353c0a97ad34cad610ee05156c19d3cf1" } }, "method": "pgp+rsa-pkcsv1.5", "type": "rsa" } }, "readme": "", "steps": [ { "_type": "step", "expected_command": [], "expected_materials": [], "expected_products": [ [ "CREATE", "*.deb" ], [ "DISALLOW", "*.deb" ] ], "name": "rebuild", "pubkeys": [ "2e7be98291270e3b7fca429a2210e99cff22017e", "918b19596d24161290d531addc4a0582b3590165" ], "threshold": 2 } ] } }apt-transport-in-toto-0.1.1/debian/000077500000000000000000000000001400430414500171335ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/debian/apt-transport-in-toto.install000077500000000000000000000004051400430414500247320ustar00rootroot00000000000000#!/usr/bin/dh-exec intoto.py => /usr/lib/apt/methods/intoto # TODO: Change root key data/intoto.conf => /etc/apt/apt.conf.d/intoto # TODO: Sign layout data/root.layout => /etc/intoto/root.layout # TODO: Copy signing and import into keychain, or use DD key? apt-transport-in-toto-0.1.1/debian/changelog000066400000000000000000000002661400430414500210110ustar00rootroot00000000000000apt-transport-in-toto (0.1.0) unstable; urgency=low * Initial Debian release. (Closes: #934143) -- Lukas Puehringer Thu, 19 Dec 2019 13:37:12 -0100 apt-transport-in-toto-0.1.1/debian/control000066400000000000000000000025111400430414500205350ustar00rootroot00000000000000Source: apt-transport-in-toto Section: utils Priority: optional Maintainer: in-toto developers Uploaders: Santiago Torres-Arias , Lukas Puehringer , Holger Levsen , Vagrant Cascadian , Justin Cappos , Build-Depends: debhelper-compat (= 13), dh-python, dh-exec, python3-all, python3-requests, python3-mock, python3-coverage, in-toto (>= 0.3.0), gnupg2, Standards-Version: 4.5.1 Rules-Requires-Root: no Homepage: https://in-toto.io Vcs-Git: https://github.com/in-toto/apt-transport-in-toto.git Vcs-Browser: https://github.com/in-toto/apt-transport-in-toto Package: apt-transport-in-toto Architecture: all Depends: ${misc:Depends}, python3, python3-requests, python3-securesystemslib, in-toto (>= 0.3.0), gnupg2, Description: apt transport method for in-toto supply chain verification apt-transport-in-toto provides a custom transport method for apt that fetches and verifies signed build information from autonomous rebuilders upon package installation. . It uses the supply chain security framework in-toto for its verification protocol, to i.a. define trust relationships and exchange and verify build information. . apt-transport-in-toto is developed at the Secure Systems Lab of NYU. apt-transport-in-toto-0.1.1/debian/copyright000066400000000000000000000016551400430414500210750ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: apt-transport-in-toto Source: https://github.com/in-toto/apt-transport-in-toto Files: * Copyright: 2018 New York University License: Apache-2.0 Copyright 2018 New York University . Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. . On Debian systems, the full text of the Apache-2.0 License can be found in the file `/usr/share/common-licenses/Apache-2.0'. apt-transport-in-toto-0.1.1/debian/rules000077500000000000000000000007721400430414500202210ustar00rootroot00000000000000#!/usr/bin/make -f # You must remove unused comment lines for the released package. #export DH_VERBOSE = 1 #export DEB_BUILD_MAINT_OPTIONS = hardening=+all #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed %: dh $@ override_dh_auto_test: # Run upstream test suite unless skipped via DEB_BUILD_OPTIONS ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) python3-coverage run -m unittest discover python3-coverage combine python3-coverage report -m endif apt-transport-in-toto-0.1.1/debian/source/000077500000000000000000000000001400430414500204335ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/debian/source/format000066400000000000000000000000151400430414500216420ustar00rootroot000000000000003.0 (native) apt-transport-in-toto-0.1.1/http000077500000000000000000000006541400430414500166230ustar00rootroot00000000000000#!/usr/bin/python3 """ http Lukas Puehringer December 06, 2018. See LICENSE for licensing information. Mock apt built-in http transport for testing. This has to be an executable in the same directory as the intoto transport. It is implemented in tests/test_intoto.py. """ import tests.test_intoto tests.test_intoto.mock_http()apt-transport-in-toto-0.1.1/intoto.py000077500000000000000000000703051400430414500176070ustar00rootroot00000000000000#!/usr/bin/python3 """ intoto.py Lukas Puehringer November 22, 2018. See LICENSE for licensing information. Provide an in-toto transport method for apt to perform in-toto verification using in-toto link metadata fetched from a rebuilder. - This program must be installed as executable in `/usr/lib/apt/methods/intoto`. - It is executed for package sources in `/etc/apt/sources.list` or `/etc/apt/sources.list.d/*`, that have an `intoto` method prefix, e.g. `deb intoto://ftp.us.debian.org/debian/ jessie main contrib` - The in-toto transport uses the http transport to download the target debian packages. - Verification is performed on `apt-get install`, i.e. after the http transport has downloaded the package requested by apt and signals apt to install it, by sending the `201 URI Done` message. - Further messages may be intercepted from apt, e.g. `601 Configuration` to parse `Config-Item`s, or `600 URI Acquire` to check if a requested URI is an index file (`Index-File: true`), issued, e.g. on `apt-get update`. - An in-toto root layout must be present on the client system, the path may be specified in the method's config file, i.e. `/etc/apt/apt.conf.d/intoto`. - Corresponding layout root keys must be present in the client gpg chain - The base path of the remote rebuilder that hosts in-toto link metadata may be specified in the client method config file. - The full path of the in-toto link metadata for a given package is inferred from the configured base path and the package URI in `600 URI Acquire`. - That information may also be used for in-toto layout parameter substitution. From the APT method interface definition:: "The flow of messages starts with the method sending out a 100 Capabilities and APT sending out a 601 Configuration. After that APT begins sending 600 URI Acquire and the method sends out 200 URI Start, 201 URI Done or 400 URI Failure. No synchronization is performed, it is expected that APT will send 600 URI Acquire messages at -any- time and that the method should queue the messages. This allows methods like http to pipeline requests to the remote server. It should be noted however that APT will buffer messages so it is not necessary for the method to be constantly ready to receive them." NOTE: From what I've seen in the message flow between apt and the http transport, apt always starts the http transport subprocess twice. When apt receives the 100 Capabilities message from the http transport it starts the transport again, and sends a 601 Configuration message. The restart prompts the http transport to resend 100 Capabilities, which probably gets ignored. After that the normal message flow continues. Below diagram depicts the message flow between apt, intoto and http (process hierarchy left to right) to successfully download a debian package and perform in-toto verification. Note that intoto or http may send 10x logging or status messages or 40x failure messages, depending on the status/results of their work. APT + intoto | + http | | + | ... | 100 Capabilities | | <-----------------+ | <-----------------+ | | 601 Configuration | ... | | +-----------------> | +-----------------> | | 600 URI Acquire | ... | | +-----------------> | +-----------------> | | ... | 200 URI Start | | <-----------------+ | <-----------------+ | | | Download package | | from archive | | 201 URI Done | | + <-----------------+ | | Download in-toto links | | and verify package | | 201 URI Done | | + <-----------------+ + + APT method interface http://www.fifi.org/doc/libapt-pkg-doc/method.html/ch2.html Apt Configuration https://manpages.debian.org/stretch/apt/apt.conf.5.en.html Apt sources list syntax https://wiki.debian.org/SourcesList """ import os import sys import signal import select import threading import logging import logging.handlers import requests import tempfile import shutil import queue as Queue # pylint: disable=import-error import subprocess import securesystemslib.gpg.functions import in_toto.exceptions import in_toto.verifylib import in_toto.models.link import in_toto.models.metadata # Configure base logger with lowest log level (i.e. log all messages) and # finetune the actual log levels on handlers logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # A file handler for debugging purposes # NOTE: bandit security linter flags the use of /tmp because an attacker might # hijack that file. This should not be a problem for logging since we don't # read from the file nor expose sensitive data, hence we exclude with #nosec # TODO: Maybe there is a better location for the log LOG_FILE = "/tmp/intoto.log" #nosec LOG_HANDLER_FILE = logging.handlers.RotatingFileHandler(LOG_FILE) LOG_HANDLER_FILE.setLevel(logging.DEBUG) logger.addHandler(LOG_HANDLER_FILE) # A stream handler (stderr), which can be configured in apt configuration file, # e.g.: APT::Intoto::LogLevel::=10 # NOTE: Use file handler above to debug events prior to apt's `601 # CONFIGURATION` message which may set the SteamHandler's loglevel LOG_HANDLER_STDERR = logging.StreamHandler() LOG_HANDLER_STDERR.setLevel(logging.INFO) logger.addHandler(LOG_HANDLER_STDERR) APT_METHOD_HTTP = os.path.join(os.path.dirname(sys.argv[0]), "http") # Global interrupted boolean. Apt may send SIGINT if it is done with its work. # Upon reception we set INTERRUPTED to true, which may be used to gracefully # terminate. INTERRUPTED = False # TODO: Maybe we can replace the signal handler with a KeyboardInterrupt # try/except block in the main loop, for better readability. def signal_handler(*junk): # Set global INTERRUPTED flag telling worker threads to terminate logger.debug("Received SIGINT, setting global INTERRUPTED true") global INTERRUPTED INTERRUPTED = True # Global BROKENPIPE flag should be set to true, if a `write` or `flush` on a # stream raises a BrokenPipeError, to gracefully terminate reader threads. BROKENPIPE = False # APT Method Interface Message definition # The first line of each message is called the message header. The first 3 # digits (called the Status Code) have the usual meaning found in the http # protocol. 1xx is informational, 2xx is successful and 4xx is failure. The 6xx # series is used to specify things sent to the method. After the status code is # an informational string provided for visual debugging # Only the 6xx series of status codes is sent TO the method. Furthermore the # method may not emit status codes in the 6xx range. The Codes 402 and 403 # require that the method continue reading all other 6xx codes until the proper # 602/603 code is received. This means the method must be capable of handling # an unlimited number of 600 messages. # Message types by their status code. Each message type has an "info" and # and the a list of allowed fields. MESSAGE_TYPE may be used to verify # the format of the received messages. CAPABILITES = 100 LOG = 101 STATUS = 102 URI_START = 200 URI_DONE = 201 URI_FAILURE = 400 GENERAL_FAILURE = 401 AUTH_REQUIRED = 402 MEDIA_FAILURE = 403 URI_ACQUIRE = 600 CONFIGURATION = 601 AUTH_CREDENTIALS = 602 MEDIA_CHANGED = 603 MESSAGE_TYPE = { # Method capabilities CAPABILITES: { "info": "Capabilities", "fields": ["Version", "Single-Instance", "Pre-Scan", "Pipeline", "Send-Config", "Needs-Cleanup"] }, # General Logging LOG: { "info": "Log", "fields": ["Message"] }, # Inter-URI status reporting (logging progress) STATUS: { "info": "Status", "fields": ["Message"] }, # URI is starting acquire URI_START: { "info": "URI Start", "fields": ["URI", "Size", "Last-Modified", "Resume-Point"] }, # URI is finished acquire URI_DONE: { "info": "URI Done", "fields": ["URI", "Size", "Last-Modified", "Filename", "MD5-Hash", # NOTE: Although not documented we need to include all these hash algos # https://www.lucidchart.com/techblog/2016/06/13/apt-transport-for-s3/ "MD5Sum-Hash", "SHA1-Hash", "SHA256-Hash", "SHA512-Hash"] }, # URI has failed to acquire URI_FAILURE: { "info": "URI Failure", "fields": ["URI", "Message"] }, # Method did not like something sent to it GENERAL_FAILURE: { "info": "General Failure", "fields": ["Message"] }, # Method requires authorization to access the URI. Authorization is User/Pass AUTH_REQUIRED: { "info": "Authorization Required", "fields": ["Site"] }, # Method requires a media change MEDIA_FAILURE: { "info": "Media Failure", "fields": ["Media", "Drive"] }, # Request a URI be acquired URI_ACQUIRE: { "info": "URI Acquire", "fields": ["URI", "Filename", "Last-Modified"] }, # Sends the configuration space CONFIGURATION: { "info": "Configuration", "fields": ["Config-Item"] }, # Response to the 402 message AUTH_CREDENTIALS: { "info": "Authorization Credentials", "fields": ["Site", "User", "Password"] }, # Response to the 403 message MEDIA_CHANGED: { "info": "Media Changed", "fields": ["Media", "Fail"] } } def deserialize_one(message_str): """Parse raw message string as it may be read from stdin and return a dictionary that contains message header status code and info and an optional fields dictionary of additional headers and their values. Raise Exception if the message is malformed. See MESSAGE_TYPE for details about formats. NOTE: We are pretty strict about the format of messages that we receive. Given the vagueness of the specification, we might be too strict. { "code": , "info": "", "fields": [ ("
", ""), ] } NOTE: Message field values are NOT deserialized here, e.g. the Last-Modified time stamp remains a string and Config-Item remains a string of item=value pairs. """ lines = message_str.splitlines() if not lines: raise Exception("Invalid empty message:\n{}".format(message_str)) # Deserialize message header message_header = lines.pop(0) message_header_parts = message_header.split() # TODO: Are we too strict about the format (should we not care about info?) if len(message_header_parts) < 2: raise Exception("Invalid message header: {}, message was:\n{}" .format(message_header, message_str)) code = None try: code = int(message_header_parts.pop(0)) except ValueError: pass if not code or code not in list(MESSAGE_TYPE.keys()): raise Exception("Invalid message header status code: {}, message was:\n{}" .format(code, message_str)) # TODO: Are we too strict about the format (should we not care about info?) info = " ".join(message_header_parts).strip() if info != MESSAGE_TYPE[code]["info"]: raise Exception("Invalid message header info for status code {}:\n{}," " message was: {}".format(code, info, message_str)) # TODO: Should we assert that the last line is a blank line? if lines and not lines[-1]: lines.pop() # Deserialize header fields header_fields = [] for line in lines: header_field_parts = line.split(":") if len(header_field_parts) < 2: raise Exception("Invalid header field: {}, message was:\n{}" .format(line, message_str)) field_name = header_field_parts.pop(0).strip() if field_name not in MESSAGE_TYPE[code]["fields"]: # pragma: no cover logger.debug("Undefined header field for message code {}: {}," .format(code, field_name)) field_value = ":".join(header_field_parts).strip() header_fields.append((field_name, field_value)) # Construct message data message_data = { "code": code, "info": info } if header_fields: message_data["fields"] = header_fields return message_data def serialize_one(message_data): """Create a message string that may be written to stdout. Message data is expected to have the following format: { "code": , "info": "", "fields": [ ("
", ""), ] } """ message_str = "" # Code must be present code = message_data["code"] # Convenience (if info not present, info for code is used ) info = message_data.get("info") or MESSAGE_TYPE[code]["info"] # Add message header message_str += "{} {}\n".format(code, info) # Add message header fields and values (must be list of tuples) for field_name, field_value in message_data.get("fields", []): message_str += "{}: {}\n".format(field_name, field_value) # Blank line to mark end of message message_str += "\n" return message_str def read_one(stream): """Read one apt related message from the passed stream, e.g. sys.stdin for messages from apt, or subprocess.stdout for messages from a transport that we open in a subprocess. The end of a message (EOM) is denoted by a blank line ("\n") and end of file (EOF) is denoted by an empty line. Returns either a message including a trailing blank line or None on EOF. """ message_str = "" # Read from stream until we get a SIGINT/BROKENPIPE, or reach EOF (see below) # TODO: Do we need exception handling for the case where we select/read from # a stream that was closed? If so, we should do it in the main loop for # better readability. while not (INTERRUPTED or BROKENPIPE): # pragma: no branch # Only read if there is data on the stream (non-blocking) if not select.select([stream], [], [], 0)[0]: continue # Read one byte from the stream one = os.read(stream.fileno(), 1).decode() # Break on EOF if not one: break # If we read something append it to the message string message_str += one # Break on EOM (and return message below) if len(message_str) >= 2 and message_str[-2:] == "\n\n": break # Return a message if there is one, otherwise return None if message_str: return message_str return None def write_one(message_str, stream): """Write the passed message to the passed stream. """ try: stream.write(message_str) stream.flush() except BrokenPipeError: # TODO: Move exception handling to main loop for better readability global BROKENPIPE BROKENPIPE = True logger.debug("BrokenPipeError while writing '{}' to '{}'.".format( message_str, stream)) # Python flushes standard streams on exit; redirect remaining output # to devnull to avoid another BrokenPipeError at shutdown # See https://docs.python.org/3/library/signal.html#note-on-sigpipe devnull = os.open(os.devnull, os.O_WRONLY) os.dup2(devnull, sys.stdout.fileno()) def notify_apt(code, message_text, uri): # Escape LF and CR characters in message bodies to not break the protocol message_text = message_text.replace("\n", "\\n").replace("\r", "\\r") # NOTE: The apt method interface spec references RFC822, which doesn't allow # LF or CR in the message body, except if followed by a LWSP-char (i.e. SPACE # or HTAB, for "folding" of long lines). But apt does not seem to support # folding, and splits lines only at LF. To be safe we escape LF and CR. # See 2.1 Overview in www.fifi.org/doc/libapt-pkg-doc/method.html/ch2.html # See "3.1.1. LONG HEADER FIELDS" and "3.1.2. STRUCTURE OF HEADER FIELDS" in # www.ietf.org/rfc/rfc822.txt write_one(serialize_one({ "code": code, "info": MESSAGE_TYPE[code]["info"], "fields": [ ("Message", message_text), ("URI", uri) ] }), sys.stdout) def read_to_queue(stream, queue): """Loop to read messages one at a time from the passed stream until EOF, i.e. the returned message is None, and write to the passed queue. """ while True: msg = read_one(stream) if not msg: return None queue.put(msg) # Dict to keep some global state, i.e. we need information from earlier # messages (e.g. CONFIGURATION) when doing in-toto verification upon URI_DONE. global_info = { "config": { "Rebuilders": [], "GPGHomedir": "", "Layout": "", "Keyids": [], "NoFail": False } } def _intoto_parse_config(message_data): """Upon apt `601 Configuration` parse intoto config items and assign to global config store. Example message data: { 'code': 601, 'info': 'Configuration' 'fields': [ ('Config-Item', 'APT::Intoto::Rebuilders::=http://158.39.77.214/'), ('Config-Item', 'APT::Intoto::Rebuilders::=https://reproducible-builds.engineering.nyu.edu/'), ('Config-Item', 'APT::Intoto::GPGHomedir::=/path/to/gpg/keyring'), ('Config-Item', 'APT::Intoto::Layout::=/path/to/root.layout'), ('Config-Item', 'APT::Intoto::Keyids::=88876A89E3D4698F83D3DB0E72E33CA3E0E04E46'), ('Config-Item', 'APT::Intoto::LogLevel::=10'), ('Config-Item', 'APT::Intoto::NoFail::=true'), ... ], } """ for field_name, field_value in message_data["fields"]: if field_name == "Config-Item" and field_value.startswith("APT::Intoto"): # Dissect config item logger.debug(field_value) _, _, config_name, config_value = field_value.split("::") # Strip leading "=", courtesy of apt config config_value = config_value.lstrip("=") # Assign exhaustive intoto configs if config_name in ["Rebuilders", "Keyids"]: global_info["config"][config_name].append(config_value) elif config_name in ["GPGHomedir", "Layout"]: global_info["config"][config_name] = config_value elif config_name == "LogLevel": try: LOG_HANDLER_STDERR.setLevel(int(config_value)) logger.debug("Set stderr LogLevel to '{}'".format(config_value)) except Exception: logger.warning("Ignoring unknown LogLevel '{}'".format(config_value)) elif config_name == "NoFail" and config_value == "true": global_info["config"][config_name] = True else: logger.warning("Skipping unknown config item '{}'".format(field_value)) logger.debug("Configured intoto session: '{}'".format(global_info["config"])) def _intoto_verify(message_data): """Upon http `201 URI Done` check if the downloaded package is in the global package store (see `_intoto_parse_package`), to filter out index files and perform in-toto verification using the session config (see `_intoto_parse_config`). Example message data: { 'code': 201, 'info': 'URI Done' 'fields': [ ('URI', 'intoto://www.example.com/~foo/debian/pool/main/cowsay_3.03+dfsg1-10_all.deb'), ('Filename', '/var/cache/apt/archives/partial/cowsay_3.03+dfsg1-10_all.deb'), ('Size', '20020'), ('Last-Modified', 'Mon, 26 Nov 2018 14:39:07 GMT'), ('MD5-Hash', '071b...'), ('MD5Sum-Hash', '071b...'), ('SHA1-Hash', '3794...'), ('SHA256-Hash', 'fd04...'), ('SHA512-Hash','95bc...'), ... ], } """ # Get location of file that was downloaded filename = dict(message_data["fields"]).get("Filename", "") uri = dict(message_data["fields"]).get("URI", "") # Parse package name and version-release according to naming convention, i.e. # packagename_version-release_architecture.deb # If we can parse packagename and version-release we will try in-toto # verification pkg_name = pkg_version_release = None if filename.endswith(".deb"): pkg_name_parts = os.path.basename(filename).split("_") if len(pkg_name_parts) == 3: pkg_name = pkg_name_parts[0] pkg_version_release = pkg_name_parts[1] if not (pkg_name and pkg_version_release): logger.info("Skipping in-toto verification for '{}'".format(filename)) return True logger.info("Prepare in-toto verification for '{}'".format(filename)) # Create temp dir verification_dir = tempfile.mkdtemp() logger.info("Create verification directory '{}'" .format(verification_dir)) logger.info("Request in-toto metadata from {} rebuilder(s) (apt config)" .format(len(global_info["config"]["Rebuilders"]))) # Download link files to verification directory for rebuilder in global_info["config"]["Rebuilders"]: # Accept rebuilders with and without trailing slash endpoint = "{rebuilder}/sources/{name}/{version}/metadata".format( rebuilder=rebuilder.rstrip("/"), name=pkg_name, version=pkg_version_release) logger.info("Request in-toto metadata from {}".format(endpoint)) try: # Fetch metadata response = requests.get(endpoint) if not response.status_code == 200: raise Exception("server response: {}".format(response.status_code)) # Decode json link_json = response.json() # Load as in-toto metadata link_metablock = in_toto.models.metadata.Metablock( signatures=link_json["signatures"], signed=in_toto.models.link.Link.read(link_json["signed"])) # Construct link name as required by in-toto verification link_name = in_toto.models.link.FILENAME_FORMAT.format( keyid=link_metablock.signatures[0]["keyid"], step_name=link_metablock.signed.name) # Write link metadata to temporary verification directory link_metablock.dump(os.path.join(verification_dir, link_name)) except Exception as e: # We don't fail just yet if metadata cannot be downloaded or stored # successfully. Instead we let in-toto verification further below fail if # there is not enought metadata logger.warning("Could not retrieve in-toto metadata from rebuilder '{}'," " reason was: {}".format(rebuilder, e)) continue else: logger.info("Successfully downloaded in-toto metadata '{}'" " from rebuilder '{}'".format(link_name, rebuilder)) # Copy final product downloaded by http to verification directory logger.info("Copy final product to verification directory") shutil.copy(filename, verification_dir) # Temporarily change to verification, changing back afterwards cached_cwd = os.getcwd() os.chdir(verification_dir) try: logger.info("Load in-toto layout '{}' (apt config)" .format(global_info["config"]["Layout"])) layout = in_toto.models.metadata.Metablock.load( global_info["config"]["Layout"]) keyids = global_info["config"]["Keyids"] gpg_home = global_info["config"]["GPGHomedir"] logger.info("Load in-toto layout key(s) '{}' (apt config)".format( global_info["config"]["Keyids"])) if gpg_home: logger.info("Use gpg keyring '{}' (apt config)".format(gpg_home)) layout_keys = securesystemslib.gpg.functions.export_pubkeys( keyids, homedir=gpg_home) else: # pragma: no cover logger.info("Use default gpg keyring") layout_keys = securesystemslib.gpg.functions.export_pubkeys(keyids) logger.info("Run in-toto verification") # Run verification in_toto.verifylib.in_toto_verify(layout, layout_keys) except Exception as e: error_msg = ("In-toto verification for '{}' failed, reason was: {}" .format(filename, str(e))) logger.error(error_msg) if (isinstance(e, in_toto.exceptions.LinkNotFoundError) and global_info["config"].get("NoFail")): logger.warning("The 'NoFail' setting was configured," " installation continues.") else: # Notify apt about the failure ... notify_apt(URI_FAILURE, error_msg, uri) # ... and do not relay http's URI Done (so that apt does not install it) return False else: logger.info("In-toto verification for '{}' passed! :)".format(filename)) finally: os.chdir(cached_cwd) shutil.rmtree(verification_dir) # If we got here verification was either skipped (non *.deb file) or passed, # we can relay the message. return True def handle(message_data): """Handle passed message to parse configuration and perform in-toto verification. The format of message_data is: { "code": , "info": "", "fields": [ ("
", ""), ] } Return a boolean value that can be used to decide, whether the message should be relayed or not. """ logger.debug("Handling message: {}".format(message_data["code"])) # Parse out configuration data required for in-toto verification below if message_data["code"] == CONFIGURATION: _intoto_parse_config(message_data) # Perform in-toto verification for non-index files # The return value decides if the message should be relayed or not elif message_data["code"] == URI_DONE: return _intoto_verify(message_data) # All good, we can relay the message return True def loop(): """Main in-toto http transport method loop to relay messages between apt and the apt http transport method and inject in-toto verification upon reception of a particular message. """ # Start http transport in a subprocess # Messages from the parent process received on sys.stdin are relayed to the # subprocess' stdin and vice versa, messages written to the subprocess' # stdout are relayed to the parent via sys.stdout. http_proc = subprocess.Popen([APT_METHOD_HTTP], stdin=subprocess.PIPE, # nosec stdout=subprocess.PIPE, universal_newlines=True) # HTTP transport message reader thread to add messages from the http # transport (subprocess) to a corresponding queue. http_queue = Queue.Queue() http_thread = threading.Thread(target=read_to_queue, args=(http_proc.stdout, http_queue)) # APT message reader thread to add messages from apt (parent process) # to a corresponding queue. apt_queue = Queue.Queue() apt_thread = threading.Thread(target=read_to_queue, args=(sys.stdin, apt_queue)) # Start reader threads. # They will run until they see an EOF on their stream, or the global # INTERRUPTED or BROKENPIPE flags are set to true. http_thread.start() apt_thread.start() # Main loop to get messages from queues, i.e. apt queue and http transport # queue, and relay them to the corresponding streams, injecting in-toto # verification upon reception of a particular message. while True: for name, queue, out in [ ("apt", apt_queue, http_proc.stdin), ("http", http_queue, sys.stdout)]: should_relay = True try: message = queue.get_nowait() logger.debug("{} sent message:\n{}".format(name, message)) message_data = deserialize_one(message) except Queue.Empty: continue # De-serialization error: Skip message handling, but do relay. except Exception as e: logger.debug("Cannot handle message, reason is {}".format(e)) else: # Read config, perform in-toto verification in there we also # decide whether we should relay the message or not. logger.debug("Handle message") should_relay = handle(message_data) if should_relay: logger.debug("Relay message") write_one(message, out) # Exit when both threads have terminated (EOF, INTERRUPTED or BROKENPIPE) # NOTE: We do not check if there are still messages on the streams or # in the queue, assuming that there aren't or we can ignore them if both # threads have terminated. if (not apt_thread.is_alive() and not http_thread.is_alive()): logger.debug("The worker threads are dead. Long live the worker threads!" "Terminating.") # If INTERRUPTED or BROKENPIPE are true it (likely?) means that apt # sent a SIGINT or closed the pipe we were writing to. This means we # should exit and tell the http child process to exit too. # TODO: Could it be that the http child closed a pipe or sent a SITERM? # TODO: Should we behave differently for the two signals? if INTERRUPTED or BROKENPIPE: # pragma: no branch logger.debug("Relay SIGINT to http subprocess") http_proc.send_signal(signal.SIGINT) return if __name__ == "__main__": signal.signal(signal.SIGINT, signal_handler) loop() apt-transport-in-toto-0.1.1/pylintrc000066400000000000000000000352771400430414500175160ustar00rootroot00000000000000[MASTER] # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code extension-pkg-whitelist= # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. ignore-patterns= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Use multiple processes to speed up Pylint. jobs=1 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= # Pickle collected data for later comparisons. persistent=yes # Specify a configuration file. #rcfile= # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED confidence= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once).You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" disable=parameter-unpacking, unpacking-in-except, long-suffix, old-ne-operator, old-octal-literal, import-star-module-level, raw-checker-failed, bad-inline-option, locally-disabled, locally-enabled, file-ignored, suppressed-message, useless-suppression, deprecated-pragma, apply-builtin, basestring-builtin, buffer-builtin, cmp-builtin, coerce-builtin, execfile-builtin, file-builtin, long-builtin, raw_input-builtin, reduce-builtin, standarderror-builtin, unicode-builtin, xrange-builtin, coerce-method, delslice-method, getslice-method, setslice-method, no-absolute-import, old-division, dict-iter-method, dict-view-method, next-method-called, metaclass-assignment, indexing-exception, raising-string, reload-builtin, oct-method, hex-method, nonzero-method, cmp-method, input-builtin, round-builtin, intern-builtin, unichr-builtin, map-builtin-not-iterating, zip-builtin-not-iterating, range-builtin-not-iterating, filter-builtin-not-iterating, using-cmp-argument, eq-without-hash, div-method, idiv-method, rdiv-method, exception-message-attribute, invalid-str-codec, sys-max-int, deprecated-str-translate-call, global-statement, broad-except, logging-not-lazy, C, R, logging-format-interpolation # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. enable= [REPORTS] # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details #msg-template= # Set the output format. Available formats are text, parseable, colorized, json # and msvs (visual studio).You can also give a reporter class, eg # mypackage.mymodule.MyReporterClass. #output-format=parseable output-format=text # Tells whether to display a full report or only the messages reports=no # Activate the evaluation score. score=yes [REFACTORING] # Maximum number of nested blocks for function / method body max-nested-blocks=5 [BASIC] # Naming hint for argument names argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct argument names argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Naming hint for attribute names attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct attribute names attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # Naming hint for class attribute names class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Naming hint for class names class-name-hint=[A-Z_][a-zA-Z0-9]+$ # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Naming hint for constant names const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 # Naming hint for function names function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct function names function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Include a hint for the correct naming format with invalid-name include-naming-hint=no # Naming hint for inline iteration names inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming hint for method names method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct method names method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Naming hint for module names module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. property-classes=abc.abstractproperty # Naming hint for variable names variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct variable names variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ [FORMAT] # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Maximum number of characters on a single line. max-line-length=80 # Maximum number of lines in a module max-module-lines=1000 # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. no-space-check=trailing-comma,dict-separator # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no [LOGGING] # Logging modules to check that the string format arguments are in logging # function parameter format logging-modules=logging [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=XXX, [SIMILARITIES] # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=yes # Minimum lines number of a similarity. min-similarity-lines=4 [SPELLING] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words=no [TYPECHECK] # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that # produce valid context managers. contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # This flag controls whether pylint should warn about no-member and similar # checks whenever an opaque object is returned when inferring. The inference # can return multiple potential results while evaluating a Python object, but # some branches might not be evaluated, which results in partial inference. In # that case, it might be useful to still emit no-member and other checks for # the rest of the inferred objects. ignore-on-opaque-inference=yes # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. ignored-classes=optparse.Values,thread._local,_thread._local, six.moves # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # Show a hint with possible names when a member name was not found. The aspect # of finding the hint is based on edit distance. missing-member-hint=yes # The minimum edit distance a name should have in order to be considered a # similar match for a missing member name. missing-member-hint-distance=1 # The total number of similar names that should be taken in consideration when # showing a hint for a missing member. missing-member-max-choices=1 [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= # Tells whether unused global variables should be treated as a violation. allow-global-unused-variables=yes # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_,_cb # A regular expression matching the name of dummy variables (i.e. expectedly # not used). dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_|junk # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.*|^ignored_|^unused_|junk # Tells whether we should check for unused import in __init__ files. init-import=no # List of qualified module names which can have objects that can redefine # builtins. redefining-builtins-modules=six.moves,future.builtins [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict, _fields, _replace, _source, _make, _generate_and_write_metadata, _delete_obsolete_metadata, _log_status_of_top_level_roles, _load_top_level_metadata, _strip_version_number, _delegated_roles, _remove_invalid_and_duplicate_signatures # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs [DESIGN] # Maximum number of arguments for function / method max-args=5 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Maximum number of boolean expressions in a if statement max-bool-expr=5 # Maximum number of branch for function / method body max-branches=12 # Maximum number of locals for function / method body max-locals=15 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of statements in function / method body max-statements=50 # Minimum number of public methods for a class (see R0903). min-public-methods=2 [IMPORTS] # Allow wildcard imports from modules that define __all__. allow-wildcard-with-all=no # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists # only in one or another interpreter, leading to false positives when analysed. analyse-fallback-blocks=no # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,TERMIOS,Bastion,rexec # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=Exception apt-transport-in-toto-0.1.1/requirements.txt000066400000000000000000000000421400430414500211710ustar00rootroot00000000000000in-toto requests securesystemslib apt-transport-in-toto-0.1.1/tests/000077500000000000000000000000001400430414500170535ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/tests/Dockerfile000066400000000000000000000023061400430414500210460ustar00rootroot00000000000000# Get slim debian base image FROM debian:sid-slim # Install required packages using apt (w/o in-toto) python, pip and gpg RUN apt-get update && apt-get install -y python-pip gpg # Install intoto and intoto apt transport (and requirements) # TODO: These should be available as apt packages RUN pip install in-toto requests subprocess32 COPY intoto.py /usr/lib/apt/methods/intoto RUN chmod +x /usr/lib/apt/methods/intoto # Copy apt configuration file, root layout and root layout key # FIXME: These should be added when installing the intoto transport COPY tests/data/intoto.conf.docker /etc/apt/apt.conf.d/intoto COPY tests/data/test.layout.docker /etc/intoto/root.layout COPY tests/data/alice.asc /etc/intoto/alice.asc RUN gpg --import /etc/intoto/alice.asc # Enable intoto apt transport # FIXME: This will be done by the user eventually (according to documentation) RUN sed -i 's/http:\/\//intoto:\/\//g' /etc/apt/sources.list # Example: apt-get install with intoto (check output!!) # NOTE: Although in-toto verification for canlock will pass, the overall # installation will fail because of the dependency libcanlock for which # we don't have rebuilder metadata ENTRYPOINT apt-get update && apt-get install canlock apt-transport-in-toto-0.1.1/tests/__init__.py000066400000000000000000000000001400430414500211520ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/tests/data/000077500000000000000000000000001400430414500177645ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/tests/data/alice.asc000066400000000000000000000032101400430414500215250ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- mQENBFwJRhkBCACqoenU2d9ds+0WwIjF0Q2+tYIO8pKC1Wxfjjlo7EvjofFLejV5 gg0brd2KsioCOjVbzOgIaDzqTf5Z64VH51qhMLQpkHuYamChUNWCImlq9LNzTX3/ Hr9Mva2K6IWa382Vy0R8gdcE1L9ICwc20Y3SnuNjDTDYu73Mqzl+J+/s2vol+zqj XEv5WQzeo+yttGdKtaqAON/kWryCyTenk++JjRb2fyTrsxW5HkYeTEdNbelcKKXp BFS2QJuJRwVMnThkueIxCtLVcIyHD4DtXvTcEmfTHZDlSEPzBVwroCR3qjBxJQj1 +GaYlTsWQ+af7N/dVtgcTpa73YxLxl4XLtd9ABEBAAG0F0FsaWNlIDxhbGljZUBh bGljZS5jb20+iQE4BBMBAgAiBQJcCUYZAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIe AQIXgAAKCRBy4zyj4OBORsNNB/4u0MS3iXJPKR+0ps/xn8G5aKcccUo+1JLNaZ8H 4WqAzLLQPRk0UgoNHXzr7anvHDKZlrgpSEuu6zJi/ysVLxqgvHMXoaVrBHndCC7g lKarOVQmFgiO9S5t3x/f+tdS+i5FDBauS3jQ0mKVkV3CPKQOq9qb5s1GPMtWIRkT Bq6T45vy5MdcgFreuvr0/SkRXdOn808InRaKZlHOOnG4Gp1jPxBFCRTbpz48jGeE UhXXP+/eygSvdpoo4Aybx9wWrKQz7GPusU8660FAN5SmmFdj+cr1H0Rp3yVPmvxe W5w5H88MEPNeiF0Ui57hPQinv9xDORgHMkp2rtWPAv6MZuwTuQENBFwJRhkBCADW Rfv/Z6hEjicX53QjMFZisiuMSjRxngWIHvMKMZDxx1sSvAkglUMv5QVBgLtBfam0 SIfnSxPIwaZ0Ljd32aadnsof7S8sLERpqS2ZutD4COC5cLp3SuoGZ096kxAL7U1J 5pOjBR6SUZeiewNZ5DT47Z3TB8rfQ67e0jkg59xE6J8LOIfPIgcXg+7Kr9Ab/EXz gHA2vwKaopb+kHH6QzUJGorX/9x+KA1NMk1TJt7zuBZ+XbFqvwNo3A7qEW42c6QM //obR4cce0QIqBlKxT9SHYQ1lvTMRmpPx7UdWr2Pf6awU1lWad9VNq0HtGO+EEXy BCZXcE52pgyuScSL0R49ABEBAAGJAR8EGAECAAkFAlwJRhkCGwwACgkQcuM8o+Dg TkYGBAf+MTvsUYRcN5tfMDsXkbmAvO1dYLvAXyhEFX6X8R1ZiS6AYlZnwVaRXyTC Qf6G3MVsjLNIRQCTdtt/wjhAO3m67zDR8I+77GVRqSzdjz+iYudjgdnDYwRXCpCe co+87M9mwkjTDEOkAW1R8s04TLPksfTrl5Cfl4ncYRBIASeklVEyYKC06OLJ1gT7 cCbEJHPe6wKto7JLXlNSEDKqCXNjJmMh4SFu68SQ15w3gc8eDqHG+ZFEjbghGx+X Z7kC5X1UmcQA+Z/ArweCi0pi+XGhYhIXab2/NareGsB9MBRhk9t31IcguKv1EUMq DeKCPetzGh3XTubL7vSl84xQ9MV1xQ== =qRqp -----END PGP PUBLIC KEY BLOCK----- apt-transport-in-toto-0.1.1/tests/data/bad/000077500000000000000000000000001400430414500205125ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/tests/data/bad/final-product_0.0.0.0-0_all.deb000066400000000000000000000000301400430414500255640ustar00rootroot00000000000000malicous binary content apt-transport-in-toto-0.1.1/tests/data/good/000077500000000000000000000000001400430414500207145ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/tests/data/good/final-product_0.0.0.0-0_all.deb000066400000000000000000000000171400430414500257730ustar00rootroot00000000000000binary solo :) apt-transport-in-toto-0.1.1/tests/data/gpg_keyring/000077500000000000000000000000001400430414500222715ustar00rootroot00000000000000apt-transport-in-toto-0.1.1/tests/data/gpg_keyring/pubring.gpg000066400000000000000000000067271400430414500244520ustar00rootroot00000000000000 \ F] l_9hKKz5y ݊*:5[h8"\ F   rԒij̲=4R |2)HK2b+/sky.9T&.mR.E Kxb]<ڛFUl^hGv۹Ua5rk!Vl\AES"-5^>s~. 8V 'ݕ 6?W8"\ G   Y?NZ-9+v*KG ׏e͍FESG5izDSJ#1|&"U=A LȎ Ji7gȣ-شKF0JK q-ՙkMTQ*HXFCV;ŭRKͽ4!SgֶMOHD kWdU.̓qYըS=dAJ3YZQ^a'!(WcayU- 4_byL1E9D[>MU \ GUg& g^KިavaA85 YmiLlJgwM?1O!l8@5`(CIB zlIN|qB54.{22{Z ϥ--LSCn ( 0tfQtI.ͬ+KGiGU1KCK|3[× ^eD9˩C?Rkum 9$Ĩv&b \ G Yg+7ӝu1~OiT"sS `bdBg{A_?4oR aH+tQ_b1 396XhUW[ dn|[Ad&]qW=I{#°:(VtF<>ol"f-XoOVӪ]0q#?G5pI\ڳThA%K솘Ji0X sm3 }z :n#e\`/UB \ S̚G >eIi;=fg!Ƽ¥uhN2zC9Sf˒(,xeԵxh8l{~sCjaVA|>5`5[%2@*OsH}04`:ڠ)Udm7p=N=#yC?:OD7ek$N =Cj!:5WaP gmd _][N@㢇\h7Carly 8"\ S   [jyƳ7XNj\yLCRSV#VZ<j'i@>Ƭf{F%j%G5A׳y]Aꊙ}$IFY=>ps'u-XdNp]ʹ-#Gs=c` [)?ZC+6~N.E H}Ob?0bzAoUʰPO!zرӝTqa" ;ԾG.%9TőoSfY+@pm9OV5“AG%QnO&[}ˆa$v ٲ$;yD+xLeL7_}QGgh@.^ZOȔڬ6|]uόغ.LVxt`(35apt-transport-in-toto-0.1.1/tests/data/gpg_keyring/random_seed000066400000000000000000000011301400430414500244670ustar00rootroot00000000000000JU 'xuq^"kYӂeY#;5iҁg_!r|pY "сt{>5Rg>l[f}>HHGCJ9:<NkE,2=5 ЇX GYA7~^yJ2=#~h7{)5+?̀_wBlryQ Udc*b!qz517s*P`0%27B=KdUc$sC*)TzU.HoMp_Fox`;NH- ԌA I~^0rgxd`(*v^@#2 q 1y% t4FQ&6"&En͓/2giGojN%Yk=L6Q{0\s: V)K_wv i>D =!=LbȒgr}l7q dگSUU##e6 =rً 3Gg5\|=lhtk%Lzޢ[)LhXj%&gapt-transport-in-toto-0.1.1/tests/data/gpg_keyring/secring.gpg000066400000000000000000000164311400430414500244270ustar00rootroot00000000000000\ F] l_9hKKz5y ݊*:5[hݭxyEtc)4c}&mZTh@ס90 cBV+Q ^Pؗ@@bdAU٬~I0pen6;i,anN] j%OK ?E"& eloƏ,Ui s36|]p@@>3s󦔿tؙy=\b8>R~܇TќN>)֫q_495#00a MEϓMYVm>őf0TՁlt?Sz!D ۆR}%&xWQkwi5G */xc[IkG2)*@5cL֗h힕|~éZD p/_Ag l%"h ]hSYR+( T BC~^fZKVɮ`SD0cp9[Ua#Qr,͏[H-PG|H㇝W'׀}TvajPf)ݝ̜1mL'}O'Xky6x9Sr.|قٯu#/Y'>Alice 8"\ F   rԒij̲=4R |2)HK2b+/sky.9T&.mR.E Kxb]<ڛF9j\ qfRD*gOJm( "'L 2Nu%o`c)1af>KdZjtz@5qtOD;=ᚎP+|=<iM̱'jmK 1fΌ&AVzO%$TH;胛=-IEyƢmq# }AX*) ס>Y'wp,z*k=VGd"<<]c51@*7 Fu)E&_-J\Q'V15(*8 %)ֈ 9"[A)1h <ˍG%',Z@-t {w>  \ F rUl^hGv۹Ua5rk!Vl\AES"-5^>s~. 8V 'ݕ 6?W Y^4 ܸ82 rv5 tSQe]9%;i? F֒ % i Xh/&}/Ű: ?/aIzw gȂ֒$G̅F-"8GjRU}`vٖbt?|? D,+lٸ+p]3`b :k+G1 7k ƺu̙i8Ejm6O4 ?KxF?dA\TAHZD;BzDFJ`g[8"\ G   Y?NZ-9+v*KG ׏e͍FESG5izDSJ#1|&"U=A LȎ Ji7gȣ-شKF0JK q-ՙkMTQ*HXFCV;ŭRKͽ4!SgֶMOHD kWdU.̓qYըS=dAJ3YZQ^a'!(WcayU- 4_byL1E9D[>MU\ GUg& g^KިavaA85 YmiLlJgwM?1O!l8@5`(CIB zlIN|qB54.{22{Z ϥ--LSCn ( 0tfQtI.ͬ+KGiGU1KCK|3[× ^eD9˩C?Rkum 9$Ĩv&b?{1@::ط>sִ#Ag@kC嗜7I 7y >QiI/{z|5L"y@8qv'3 ,v~،5 nZ%u[yw\ʇhUTù}8Tێk%kXr]>0c&MHG3&TU}`hE(Hg׹")R2,1}jqʕ~iS.Eז9UzQ<,.-U*i":/ ox\Nc O_3i PnF$;1+fO ;NtG tX"knm]ARQPq: 9o1!dlQi%Ų0hI}0cKׇ"O09aU)ALD/󌰖lr/Y(>NU)R ]in:6оIo1.k/Hޚ S-?\SvH:AVlc*6#LY*Mb%`T)SWV_8/ ḃtTE \ G Yg+7ӝu1~OiT"sS `bdBg{A_?4oR aH+tQ_b1 396XhUW[ dn|[Ad&]qW=I{#°:(VtF<>ol"f-XoOVӪ]0q#?G5pI\ڳThA%K솘Ji0X sm3 }z :n#e\`/UB\ S̚G >eIi;=fg!Ƽ¥uhN2zC9Sf˒(,xeԵxh8l{~sCjaVA|>5`5[%2@*OsH}04`:ڠ)Udm7p=N=#yC?:OD7ek$N =Cj!:5WaP gmd _][N@㢇\h7 6i樂 8Pҍຢؙw0p_!Re̎+_CέRJ:J6xM{.Pd+XK2ϩuK vCg;cU+[in M<x߯/STxک~*OH e t kr_@A>}8%ҡv?u"\ -GWeg=ܙLL$"`@y4޸(Ⱦg -3qO5 㺨+#G?朤}/tK6*6|"/jV,l8gНlH ݱX j߈A/g3?hJśdPwgo}F/B(The5;1xG}˫x 8)6OBe`8ǧ`6XLl+"KA{CuJp,=Ih.:v|jc /C4Qqh2Ռa KkdG%N/ꔆ eRf!8"\ S   [jyƳ7XNj\yLCRSV#VZ<j'i@>Ƭf{F%j%G5A׳y]Aꊙ}$IFY=>ps'u-XdNp]ʹ-#Gs=c` [)?ZC+6~N.E H}O6*K8@W8cj W gjyN)9GH'y$`3 Kpc\RȘ2UP?e|*zn(9hl? :¡e7^⻕E";ȊvR:q8==t x͈#vwyEN=N#t] dM[* >seڋS4x#Sv*,D>08P& P15fN_F e7T1 ΙItʟj,Fj V~#F jn@=̚.(|W=NDVs,_v8s{ >KTGY䁑P8xK+B7hّ#ګqD1|AEU&w~|Zz e%1 %n"`S0}'.(Ӯ͆>u_ ?KG(̎aHʇ>vpȔ<.\*Uy,FU \ S [jyƳ[B{JR.҂7j=:uq&kYf>b?0bzAoUʰPO!zرӝTqa" ;ԾG.%9TőoSfY+@pm9OV5“AG%QnO&[}ˆa$v ٲ$;yD+xLeL7_}QGgh@.^ZOȔڬ6|]uόغ.LVxt`(35apt-transport-in-toto-0.1.1/tests/data/gpg_keyring/trustdb.gpg000066400000000000000000000027601400430414500244640ustar00rootroot00000000000000gpg\ T "  $ 4&Hjp-1 jFk.Ih}IP' jirz( F`vփXNh[jyƳ% ȟ|ܒ()gaͻapt-transport-in-toto-0.1.1/tests/data/intoto.conf.docker000066400000000000000000000004021400430414500234110ustar00rootroot00000000000000APT::Intoto { LogLevel {"20"}; Rebuilders { "http://158.39.77.214"; "https://reproducible-builds.engineering.nyu.edu"; }; Layout {"/etc/intoto/root.layout"}; Keyids { "88876A89E3D4698F83D3DB0E72E33CA3E0E04E46" }; NoFail {"true"} }; apt-transport-in-toto-0.1.1/tests/data/rebuild.5863835e.link000066400000000000000000000017401400430414500233040ustar00rootroot00000000000000{ "signatures": [ { "keyid": "5863835e5ec8e640fa24410f069edc1d59b58507", "other_headers": "04000108001d1621045863835e5ec8e640fa24410f069edc1d59b5850705025c095e65", "signature": "5206db710573e852a89c901a51d8207d3628b67f5bbcb8dd7c6eaf9861a7f0b898a355e54f19472c82e454967a4b6707aefe96e02b1364216f9ddac12989a75dee0798de466e6d58c40598c7a72cc0944100e1c7f8504bfacb959a1cae759d377d01a2149c1d89669e99cde162f0131f458312180e280cefe98d59efe081e6102d0cd644c00f84dd545e134d3b403e99753c114f78a9f72ab03cf710503e07abdea90b99280cc984cf625f1c28f9c52b9ae23c348ef29d17ac47fa1df9a31008d0d209089cf9955055ee1eab418d1036fbe224a506a7ba15a1b763c0fc649efaa5b43f62c0119219fac9dd1911c85afdaa2fb68f39131dfc1b67063d13547547" } ], "signed": { "_type": "link", "byproducts": {}, "command": [], "environment": {}, "materials": {}, "name": "rebuild", "products": { "final-product_0.0.0.0-0_all.deb": { "sha256": "95b066f599e599e67f379bf36e696fe6d59ad4c07b7e026189cdb3f353c94eb1" } } } }apt-transport-in-toto-0.1.1/tests/data/rebuild.e946fc60.link000066400000000000000000000017401400430414500234400ustar00rootroot00000000000000{ "signatures": [ { "keyid": "e946fc6076d683584e6803dbd35bad6a79a3c6b3", "other_headers": "04000108001d162104e946fc6076d683584e6803dbd35bad6a79a3c6b305025c095e7a", "signature": "3fd6f3ef7ce2f0f8eccd92ef176bb95f2f49da327ac95609df23a19310dc32de05d02c0f79ad57aa2a070a13c209ff722233b41fe0c25c9be771739532536cd27224a975eaf53d621873fd8b10094fefaf8324e2b47d6ce2ceb3ee1b9e3ff8abcfb316c2a91a50fd798ae050a5d86fc693b07d18ac2e664965a71b7069c0c6ff164d08a449e885bf7edc06433d10ede6544820cd16cdf37301eb390647e218d4c5cef14c1e9bfcaab6f26de0f972288c8c09abe90a80c771d699a9864b2485ce7a55cfe46c79459d1985a4d5bfc1f7471694f3798ad00520a9bddb90e907689d647949b33581d23d425d828cb52be75abb46504c5615e420fdbdef4c991e84ff" } ], "signed": { "_type": "link", "byproducts": {}, "command": [], "environment": {}, "materials": {}, "name": "rebuild", "products": { "final-product_0.0.0.0-0_all.deb": { "sha256": "95b066f599e599e67f379bf36e696fe6d59ad4c07b7e026189cdb3f353c94eb1" } } } }apt-transport-in-toto-0.1.1/tests/data/test.layout000066400000000000000000000114761400430414500222130ustar00rootroot00000000000000{ "signatures": [ { "keyid": "88876a89e3d4698f83d3db0e72e33ca3e0e04e46", "other_headers": "04000108001d16210488876a89e3d4698f83d3db0e72e33ca3e0e04e460502600eaa72", "signature": "4e03ee1b0ad69bc581b6e91bd7760db932020b1a3233bb09b7cf4bb118187f1c2ab19c43169399ef609835f829164f86da440dce4299d5dac027f5fddf1ef6ae6b6828de1d2fac34415a318c3b235fdc352543837a70d5bc2693af25a48c76c0d37c31ce9fdd2aa6f6c0f3cdb03db6ab710ed4e3f10ca51ca8a39fd1d6237f0909e0cea257b579b66db13b3451a597011670bf153b406c5b7218762e31ea6ec9294cc4058da5639970c069281cb8bfa3adaff505c3967dc55128be85150dc1d041f0e0bacd2dc38e535534228cab86f0954205ca24453a99a0f1758ec386100c9a4d370849728e42fc2720a6fa8bda3984eecb5eba28ee8008e9e7ae33a9a4aa" } ], "signed": { "_type": "layout", "expires": "2030-01-01T00:00:00Z", "inspect": [ { "_type": "inspection", "expected_materials": [ [ "MATCH", "*.deb", "WITH", "PRODUCTS", "FROM", "rebuild" ], [ "DISALLOW", "*.deb" ] ], "expected_products": [], "name": "verify-reprobuilds", "run": [ "true" ] } ], "keys": { "5863835e5ec8e640fa24410f069edc1d59b58507": { "hashes": [ "pgp+SHA2" ], "keyid": "5863835e5ec8e640fa24410f069edc1d59b58507", "keyval": { "private": "", "public": { "e": "010001", "n": "e5742007525e17ee22f6d5223dbd471c37b30d988398d82d875a75ab639045e0a0efdc2b6a0865951291beb326e46cf5d1443fdc3a3060d1b0d077ac046df065ede6ca33dee5ab8e24f329ab5bebb2bc2416a842f8b55ba3edcbac1636c7f25e0ff60e551b4c60af1cc8c867c9679532d099537e600dd17cf306d44f3089faa06120e9d93edde2ce0fb6a2556cbc5ee286ce680292c21fae9647768bdbb955e76135726bd5de0321567ff7ac9e00ba6c5c4145fcee19f10f5303ac22fa80ae9bf3f1852d35085e8fbbc4c5fa3e0073e9a87e2e0afb1038e5ab5602f6951c0d820d93271fdd95f709e836159c3f573c9f7428f0ba4cd8e0b5b8b98cd523fb93fb" } }, "method": "pgp+rsa-pkcsv1.5", "subkeys": { "8357b173d137d2482eb2707bf12a7ffdbd73613c": { "hashes": [ "pgp+SHA2" ], "keyid": "8357b173d137d2482eb2707bf12a7ffdbd73613c", "keyval": { "private": "", "public": { "e": "010001", "n": "bd5567260f20dc7faf67dd5ee4d34b87a6eedea861d01776619cb841f6f338350bc6596d9abc694c6cc0eb844a1f67774d3fbc0f7fd9e4314fdfe0dc21b96c8cb81b087fb80206eec3f938403560e228439549420b7a02d46cee494ef1f37c1aa37142f7f41cfc0e35342e7b95d4e995d3db06eb320f327b5a0d090506cfa52d88cfe88f2d961a86e94c5343836e8d0cd628adff0d30746651b8e974bd492ec7cdac102bfe4ba2476947c3f41eabf7e955a8120293314b15fdfc9ae4abd743064bbcad7c335bbff091c3970a17859dcf5e6544e339eebaaed4f49acccba9431bfdad97a23fe695526bdb756d1c092014b739bf24adc4a8b8d876b126168d62c9" } }, "method": "pgp+rsa-pkcsv1.5", "type": "rsa" } }, "type": "rsa" }, "e946fc6076d683584e6803dbd35bad6a79a3c6b3": { "hashes": [ "pgp+SHA2" ], "keyid": "e946fc6076d683584e6803dbd35bad6a79a3c6b3", "keyval": { "private": "", "public": { "e": "010001", "n": "cc9a47d71a0cafb0af9e1796a03e65f7e149cb69d53bd2f43d6667d3dbd621c6bcf398c2a575add9d81b684e327a8b4339979583e4f653b90666a59408cb9205e2289d2c07bd80ae8c1778fdb9c365d4b578f404689202ef381a108e6c8f987bb17e73cb43c9d3e61c6ad30f61195641a10f7cef3e1c3560355b25fea932921ebe1088402a4ff473487d193002dd34c3603adaa029ba559b9f646d3770b73d4e3d1600e498238479430506a5d83fb1ba3a4f44029a14df37dd656beda080248c8b10924e0d3d7fa943196a213aac3557e26150908ffbe2f8100d67996da7b28b64030cd218811bc0915fe75dae5ba29d4e40e3a287edf998f25cce6811afbc37" } }, "method": "pgp+rsa-pkcsv1.5", "subkeys": { "0dc0740edcd16f3c930b5d585cfdcc6b17f3ff27": { "hashes": [ "pgp+SHA2" ], "keyid": "0dc0740edcd16f3c930b5d585cfdcc6b17f3ff27", "keyval": { "private": "", "public": { "e": "010001", "n": "b2b5322e09f50e6f40a5de14888870c3874e2a9206a3da4b62c3cf47ba97a53b8a8caf6c8046356db3036cd57a2dfa5573615efb7df0924eeb6a0a1a3a41b313a6506a583242c9f0ce7d21b15117642aa6ee29dadffc1a469430c7e7539e814dd2ae75071cf3a0a9c0a6a613329d9254bc14a6e9b94356d68d38d4f9fea2cce7e86873005d49dc5a6fa7bcedd5c9a41e2f5f7bd4217b73fda697354ebbd637d36d7b4abca0f136bf102b0483c7036e341c70cc555ea6d5ea169316473ca692947e29c8deef5d6501c9c5bb9edb8eb8a3b5ff83bd64b511100368c8901ca66fcde7ee01b318c006568aa3446e1a44f51a534611bc41d032199aab8beba1a4fa5b" } }, "method": "pgp+rsa-pkcsv1.5", "type": "rsa" } }, "type": "rsa" } }, "readme": "", "steps": [ { "_type": "step", "expected_command": [], "expected_materials": [], "expected_products": [ [ "CREATE", "*.deb" ], [ "DISALLOW", "*.deb" ] ], "name": "rebuild", "pubkeys": [ "5863835e5ec8e640fa24410f069edc1d59b58507", "e946fc6076d683584e6803dbd35bad6a79a3c6b3" ], "threshold": 2 } ] } }apt-transport-in-toto-0.1.1/tests/data/test.layout.docker000066400000000000000000000054311400430414500234530ustar00rootroot00000000000000{ "signatures": [ { "keyid": "88876a89e3d4698f83d3db0e72e33ca3e0e04e46", "other_headers": "04000108001d16210488876a89e3d4698f83d3db0e72e33ca3e0e04e460502600eaa7a", "signature": "0d09b045bb7baabf8a099830e24a0e36454253f811fda0d0b12fc99c1bcace9bd79aee921e1ebecbc7134fc3c0de584b38aca2e793ce2a77c1c66d91db33817ba8ca1021a77a9d0cce535761ce1c247e20e46637737cd2a954de4d572b2a3100756f150cb7544772e4a456b392219a694b1a41ce56d7016056a1b00da585db7303db706b7942d08e33b13b55569c541d98f8b6d19dfb8847d71cf853196f69ff30571ba875c8a673943262209c2ea725379590ff95ab179529874860da0aba6eba5c4299af7e5efaa13969472a08c6c3d0cff26b67b791ac1aca186d5df979588268e2593cc100c24cc0d6ca286eb321b64fea105733bb45cbfa6046b89640f0" } ], "signed": { "_type": "layout", "expires": "2030-01-01T00:00:00Z", "inspect": [ { "_type": "inspection", "expected_materials": [ [ "MATCH", "*.deb", "WITH", "PRODUCTS", "FROM", "rebuild" ], [ "DISALLOW", "*.deb" ] ], "expected_products": [], "name": "verify-reprobuilds", "run": [ "/usr/bin/true" ] } ], "keys": { "2e7be98291270e3b7fca429a2210e99cff22017e": { "hashes": [ "pgp+SHA2" ], "keyid": "2e7be98291270e3b7fca429a2210e99cff22017e", "keyval": { "private": "", "public": { "e": "010001", "n": "e0da84becb294c355f9d586cb9c14e4e7707db0ccd301d41b4926d34602a35e62f26b5c092c7bb48b8c196e2506c45882b3098788f81663b079eadc61e2a40b7059032c9865059e967d7fa01a816849c646f8d9d5b7f7c0a57920bb05e2aec8e5c7116a09f693d4ed39c13fe7f53191035f4265d1f3b68e37987da5c300aa03b987b86a9d3d7e10e48a67b5631386e10b2d2832a984ddb3706d672c49575c78f8d3d1ce0a195466feb7604a2e04a28b1aa44879c812b180c453cd1d5494e48fde42cc3970d0267a39e41ba4e5e116812e3ade8dcc5e6875cb1df12349f9936d849d6dd3e11ca1067ab70c0dfd0a3770c49d239fa7fdb2a5d47963578deb5c8a6ab1460d986d9bef4ea42b90913b35d7b121bc83ef21f6872ea5bb898fdaa5ccd028a2c7ea5c89c30202b035a7bd5eededca1475a77c565092d8629d1250a9d658373fd9026b2bb72662835fb09bcf73c4256931435f72040e771f3ecaab3b3056ffe699290385211cf276528b5867e868a5df5ec1e5631313b3145de9faed46544653f9073ec55c2da962e6fbc8f9f603348e3d8b55eec078af83b2e6d0d15adacbb4bf212a3e72c806322e84255c85ea3e33d1702942833837afdf71f0068c3bdf9a2b6c3ab3bae309b13466a05ebad14c1cd37c993af0d2a34f42ba10c3630cf2da6a0804186bc2cfd2e4be1995c631527fc61e28bdf7a62e9f3f3f5e5f27f" } }, "method": "pgp+rsa-pkcsv1.5", "type": "rsa" } }, "readme": "", "steps": [ { "_type": "step", "expected_command": [], "expected_materials": [], "expected_products": [ [ "CREATE", "*.deb" ], [ "DISALLOW", "*.deb" ] ], "name": "rebuild", "pubkeys": [ "2e7be98291270e3b7fca429a2210e99cff22017e" ], "threshold": 1 } ] } }apt-transport-in-toto-0.1.1/tests/measure_coverage.py000066400000000000000000000020641400430414500227430ustar00rootroot00000000000000#!/usr/bin/python3 """ measure_coverage.py Lukas Puehringer December 21, 2018. """ import sys import coverage # Setup code coverage measurement (will look for COVERAGE_PROCESS_START envvar) coverage.process_startup() # The first argument must be the actual executable exectuable = sys.argv[1] # Patch sys.argv so that the executable thinks it was called directly sys.argv = [exectuable] # Execute executable in this process measuring code coverage with open(exectuable) as f: code = compile(f.read(), exectuable, "exec") exec(code) apt-transport-in-toto-0.1.1/tests/serve_metadata.py000066400000000000000000000026471400430414500224220ustar00rootroot00000000000000#!/usr/bin/python3 """ serve_metadata.py Lukas Puehringer December 10, 2018. test_intoto.py Lukas Puehringer December 06, 2018. See LICENSE for licensing information. Mock basic apt <--> intoto <--> http message flow. The main TestCase below mocks (apt), which calls the intoto transport in a subprocess, reading and writing messages according to the basic message flow. The intoto transport starts the mock http transport (implemented in mock_http) also in a subprocess, which reads relayed messages and replies accordingly. python -m unittest tests.test_intoto """ import os import sys import unittest import signal import intoto import logging if sys.version_info[0] == 2: import subprocess32 as subprocess else: import subprocess LOG_LEVEL = logging.INFO TEST_PATH = os.path.dirname(os.path.realpath(__file__)) TEST_DATA_PATH = os.path.join(TEST_PATH, "data") # Paths to two versions of the final product, one with a hash that matches # the product of the rebuild step (as per link metadata) and one that does not. FINAL_PRODUCT_PATH_GOOD = os.path.join(TEST_DATA_PATH, "good", "final-product_0.0.0.0-0_all.deb") FINAL_PRODUCT_PATH_BAD = os.path.join(TEST_DATA_PATH, "bad", "final-product_0.0.0.0-0_all.deb") # Absolute path to intoto transport. It will use this path (argv[0]) # to find the http transport. INTOTO_EXEC = os.path.join(TEST_PATH, "..", "intoto.py") # The mock apt routine of this test does not call the intoto transport directly # but instead calls a shim that enables subprocess test code coverage MEASURE_COVERAGE = os.path.join(TEST_PATH, "measure_coverage.py") # The subprocess test code coverage requires below envvar os.environ['COVERAGE_PROCESS_START'] = os.path.join(TEST_PATH, "..", ".coveragerc") # Path to mock rebuilder server executable MOCK_REBUILDER_EXEC = os.path.join(TEST_PATH, "serve_metadata.py") # Default values for the `601 Configuration` and `600 URI Acquire` message # used in tests below. May be overridden in a test. _CONFIG_DEFAULTS = { "log_level": LOG_LEVEL, "rebuilder1": "http://127.0.0.1:8081", "rebuilder2": "http://127.0.0.1:8082", "layout_path": os.path.join(TEST_DATA_PATH, "test.layout"), "layout_keyid": "88876A89E3D4698F83D3DB0E72E33CA3E0E04E46", "gpg_home": os.path.join(TEST_DATA_PATH, "gpg_keyring"), "no_fail": "false" } _ACQUIRE_DEFAULTS = { "filename": FINAL_PRODUCT_PATH_GOOD } # Message scaffoldings used in tests below. `_MSG_CAPABILITIES` and # `_MSG_URI_DONE` are sent by the mock http transport and `_MSG_CONFIG` and # `_MSG_ACQUIRE` by the mock apt. _MSG_CAPABILITIES = \ """100 Capabilities """ _MSG_CONFIG = \ """601 Configuration Config-Item: APT::Intoto::Rebuilders::={rebuilder1} Config-Item: APT::Intoto::Rebuilders::={rebuilder2} Config-Item: APT::Intoto::LogLevel::={log_level} Config-Item: APT::Intoto::Layout::={layout_path} Config-Item: APT::Intoto::Keyids::={layout_keyid} Config-Item: APT::Intoto::GPGHomedir::={gpg_home} Config-Item: APT::Intoto::NoFail::={no_fail} """ _MSG_ACQUIRE = \ """600 URI Acquire Filename: {filename} """ _MSG_URI_DONE = \ """201 URI Done Filename: {filename} """ def mock_http(): """Mock basic http transport series of message writes and reads. """ try: # Send capabilities intoto.write_one(_MSG_CAPABILITIES, sys.stdout) # Wait for CONFIGURATION and ignore intoto.read_one(sys.stdin) # Wait for URI Acquire acquire_msg = intoto.deserialize_one(intoto.read_one(sys.stdin)) # send URI Done intoto.write_one(_MSG_URI_DONE.format( filename=dict(acquire_msg["fields"]).get("Filename", "")), sys.stdout) except KeyboardInterrupt: return def mock_apt(intoto_proc, config_args=None, acquire_args=None): """Mock basic apt series of message reads and writes. Messages scaffoldings and message parameters are defined above but may be overriden using arguments config_args and acquire_args. """ if not config_args: config_args = {} if not acquire_args: acquire_args = {} # Wait for Capabilities, intoto.read_one(intoto_proc.stdout) # Send Config intoto.write_one(_MSG_CONFIG.format( **dict(_CONFIG_DEFAULTS, **config_args)), intoto_proc.stdin) # Send URI Acquire intoto.write_one(_MSG_ACQUIRE.format( **dict(_ACQUIRE_DEFAULTS, **acquire_args)), intoto_proc.stdin) # Wait for URI Done return intoto.deserialize_one(intoto.read_one(intoto_proc.stdout)) class InTotoTransportTestCase(unittest.TestCase): """Test class to mock routines as would be triggered by `apt-get install`. Each test mocks an installation with different parameters, asserting for passing or failing in-toto verification. """ @classmethod def setUpClass(self): """Start two mock rebuilder servers on localhost, each of which serving a rebuild link metadata as required by the layout and the final product used in below tests. These rebuilders may be re-used for all tests of this class. """ # The request both servers listen for to serve metadata_file defined below self.metadata_request = "/sources/final-product/0.0.0.0-0/metadata" self.rebuilder_procs = [] for port, metadata_file in [ ("8081", "rebuild.5863835e.link"), ("8082", "rebuild.e946fc60.link")]: metadata_path = os.path.join(TEST_DATA_PATH, metadata_file) self.rebuilder_procs.append(subprocess.Popen(["python3", MOCK_REBUILDER_EXEC, port, self.metadata_request, metadata_path], stderr=subprocess.DEVNULL)) @classmethod def tearDownClass(self): """Tell mock rebuilder servers to shutdown and wait until they did. """ for rebuilder_proc in self.rebuilder_procs: rebuilder_proc.send_signal(signal.SIGINT) for rebuilder_proc in self.rebuilder_procs: rebuilder_proc.wait() def setUp(self): """Start intoto transport anew for each test. """ self.intoto_proc = subprocess.Popen( ["python3", MEASURE_COVERAGE, INTOTO_EXEC], stdin=subprocess.PIPE, stdout=subprocess.PIPE, # stderr=subprocess.DEVNULL, # NOTE: Unomment for less verbose test log universal_newlines=True) def tearDown(self): """Tell intoto transport to shutdown and wait until it did. We also have to close the opened subprocess pipes. """ self.intoto_proc.stdin.close() self.intoto_proc.stdout.close() self.intoto_proc.send_signal(signal.SIGINT) self.intoto_proc.wait() def test_pass(self): """Verification passes. """ result = mock_apt(self.intoto_proc) self.assertEqual(result["code"], intoto.URI_DONE) def test_bad_target(self): """Verification fails due to final product with bad hash. """ result = mock_apt(self.intoto_proc, acquire_args={"filename": FINAL_PRODUCT_PATH_BAD}) self.assertEqual(result["code"], intoto.URI_FAILURE) def test_bad_target_nofail(self): """Verification fails due to final product with bad hash despite nofail option. NOTE: Nofail is only used if the fail reason is missing links. """ result = mock_apt(self.intoto_proc, config_args={"no_fail": "true"}, acquire_args={"filename": FINAL_PRODUCT_PATH_BAD}) self.assertEqual(result["code"], intoto.URI_FAILURE) def test_missing_links(self): """Verification fails due to missing links. """ # Override address of one rebuilder (there is no rebuilder on port 8083) result = mock_apt(self.intoto_proc, config_args={"rebuilder1": "http://127.0.0.1:8083"}) self.assertEqual(result["code"], intoto.URI_FAILURE) def test_missing_links_nofail(self): """Verification passes despite missing links because of nofail option. """ result = mock_apt(self.intoto_proc, config_args={"rebuilder1": "http://127.0.0.1:8083", "no_fail": "true"}) self.assertEqual(result["code"], intoto.URI_DONE) def test_missing_links_404(self): """Verification fails due to missing links (rebuilder 404). """ # Start another mock rebuilder that 404s on the expected request # To be sure, let's first assert that it will indeed not find anything self.assertFalse( os.path.exists(os.path.abspath(self.metadata_request.lstrip("/")))) rebuilder_proc = subprocess.Popen( ["python3", MOCK_REBUILDER_EXEC, "8083", "/", "/"], stderr=subprocess.DEVNULL) result = mock_apt(self.intoto_proc, config_args={"rebuilder1": "http://127.0.0.1:8083"}) self.assertEqual(result["code"], intoto.URI_FAILURE) rebuilder_proc.send_signal(signal.SIGINT) rebuilder_proc.wait() if __name__ == "__main__": unittest.main() apt-transport-in-toto-0.1.1/tests/test_units.py000066400000000000000000000104151400430414500216270ustar00rootroot00000000000000#!/usr/bin/python3 """ test_units.py Lukas Puehringer Jan 02, 2019. See LICENSE for licensing information. Test individual selected units of the intoto transport that are not covered by the more holistic tests in `test_intoto.py`, which mock and test full use case scenarios. python -m unittest tests.test_units """ import mock import unittest import intoto from intoto import (serialize_one, deserialize_one, _intoto_parse_config, _intoto_verify, LOG_HANDLER_STDERR) class TestSerialization(unittest.TestCase): """Test parts serialization and deserialization functions. """ def test_serialize_deserialize(self): """Test that data is (de)serialized as expected. """ msg = "601 Configuration\nConfig-Item: 1\nConfig-Item: 2\n\n" msg_data = { "code": 601, "info": "Configuration", "fields": [ ("Config-Item", "1"), ("Config-Item", "2"), ] } self.assertEqual(deserialize_one(msg), msg_data) self.assertEqual(serialize_one(msg_data), msg) def test_deserialize_error(self): """Test deserialization errors on malformed data. """ for msg, error in [ ("", "Invalid empty message:"), ("10000", "Invalid message header:"), ("LOL LOL", "Invalid message header status code:"), ("1000 LOL", "Invalid message header status code:"), ("100 LOL", "Invalid message header info"), ("601 Configuration\nConfig-Item", "Invalid header field:")]: with self.assertRaises(Exception) as ctx: deserialize_one(msg) self.assertIn(error, str(ctx.exception)) class TestConfigParser(unittest.TestCase): """Test function that parses the `601 Configuration` message. """ def test_log_level_config(self): """Test parsing LogLevel config. """ def _intoto_parse_config_with_log_level(level): """Wrapper for _intoto_parse_config to pass message with specific log level. """ _intoto_parse_config({ "code": 601, "info": "Configuration", "fields": [ ("Config-Item", "APT::Intoto::LogLevel::={}".format(level)), ], }) # Backup log level level_backup = LOG_HANDLER_STDERR.level # Test with bad log level values for level in ["1.0", "abc"]: _intoto_parse_config_with_log_level(level) self.assertNotEqual(LOG_HANDLER_STDERR.level, level) # Test with good log level values _intoto_parse_config_with_log_level(100) self.assertEqual(LOG_HANDLER_STDERR.level, 100) # Restore log level LOG_HANDLER_STDERR.level = level_backup def test_ignore_config_items(self): """Test that irrelevant configs are ignored. """ empty_global_info = { "config": { "Rebuilders": [], "GPGHomedir": "", "Layout": "", "Keyids": [], "NoFail": False } } # Backup and reset global info backup_global_info = intoto.global_info intoto.global_info = empty_global_info # Call config parsing function with irrelevant configs _intoto_parse_config({ "code": 601, "info": "Configuration", "fields": [ ("No-Config-Item", "123"), ("Config-Item", "APT::Other::Info"), ], }) # Global info should still be empty self.assertDictEqual(intoto.global_info, empty_global_info) # Restore global info intoto.global_info = backup_global_info class TestVerification(unittest.TestCase): """Test function that triggers intoto verification (upon reception of `201 URI Done` message). """ def test_skip_wrong_name(self): """Skip in-toto verification for files with wrong filename. """ def _intoto_verify_with_filename(fn): """Wrapper for _intoto_verify to pass message with specific filename. """ _intoto_verify({ "code": 201, "info": "URI Done", "fields": [ ("Filename", "{}".format(fn)), ], }) for fn in ["not-a-deb.txt", "way_too_may_party.deb", "missing_parts.deb"]: with mock.patch("intoto.logger") as mock_logger: _intoto_verify_with_filename(fn) mock_logger.info.assert_called_with( "Skipping in-toto verification for '{}'".format(fn)) if __name__ == "__main__": unittest.main()apt-transport-in-toto-0.1.1/tox.ini000066400000000000000000000020741400430414500172270ustar00rootroot00000000000000# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. # To run an individual test environment run e.g. tox -e py38 [tox] skipsdist = True envlist = py39 [testenv] deps = -r requirements.txt pylint bandit coverage mock setenv = PYTHONPATH={envsitepackagesdir} commands = # Run pylint, using secure system lab's pylintrc configuration file # https://github.com/secure-systems-lab/sample-documents/blob/master/pylintrc pylint intoto.py # Run bandit, a security linter from OpenStack Security # We need to import subprocess to run the apt http transport # https://docs.openstack.org/bandit/latest/blacklists/blacklist_imports.html#b404-import-subprocess bandit intoto.py --skip B404 # Run tests generating coverage {envpython} -m coverage run -m unittest discover {envpython} -m coverage combine {envpython} -m coverage report -m