From def669dd1d5a247ea033d094971b07fbfdbbe9f2 Mon Sep 17 00:00:00 2001 From: Al Finkelstein Date: Fri, 31 Jul 2020 10:59:34 -0500 Subject: [PATCH] Add Python packages fetchers and checks (#5) --- CHANGES.md | 4 + arboretum/__init__.py | 2 +- arboretum/common/constants.py | 18 ++ arboretum/technology/auditree/README.md | 140 +++++++++++---- .../auditree/checks/test_python_packages.py | 162 ++++++++++++++++++ .../evidences/python_package_release.py | 38 ++++ .../fetchers/fetch_python_packages.py | 91 ++++++++++ .../auditree/abandoned_evidence.md.tmpl | 49 ++++-- .../auditree/compliance_config.md.tmpl | 19 +- .../reports/auditree/python_packages.md.tmpl | 71 ++++++++ controls.json | 5 + test/fixtures/pypi_release_info.xml | 45 +++++ test/test_evidences/__init__.py | 15 ++ .../test_python_package_release.py | 47 +++++ 14 files changed, 644 insertions(+), 62 deletions(-) create mode 100644 arboretum/common/constants.py create mode 100644 arboretum/technology/auditree/checks/test_python_packages.py create mode 100644 arboretum/technology/auditree/evidences/python_package_release.py create mode 100644 arboretum/technology/auditree/fetchers/fetch_python_packages.py create mode 100644 arboretum/technology/auditree/templates/reports/auditree/python_packages.md.tmpl create mode 100644 test/fixtures/pypi_release_info.xml create mode 100644 test/test_evidences/__init__.py create mode 100644 test/test_evidences/test_python_package_release.py diff --git a/CHANGES.md b/CHANGES.md index 46d0dfd0..e863571b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +# 0.2.0 + +- [NEW] Add Python packages fetcher and check. + # 0.1.0 - [NEW] Add compliance configuration fetcher and check. diff --git a/arboretum/__init__.py b/arboretum/__init__.py index a1adfd09..573376ee 100644 --- a/arboretum/__init__.py +++ b/arboretum/__init__.py @@ -14,4 +14,4 @@ # limitations under the License. """Arboretum - Checking your compliance & security posture, continuously.""" -__version__ = '0.1.0' +__version__ = '0.2.0' diff --git a/arboretum/common/constants.py b/arboretum/common/constants.py new file mode 100644 index 00000000..2a914400 --- /dev/null +++ b/arboretum/common/constants.py @@ -0,0 +1,18 @@ +# -*- mode:python; coding:utf-8 -*- +# Copyright (c) 2020 IBM Corp. All rights reserved. +# +# 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. +"""Common constants.""" + +# PyPI RSS feed base URL +PYPI_RSS_BASE_URL = 'https://pypi.org/rss/project' diff --git a/arboretum/technology/auditree/README.md b/arboretum/technology/auditree/README.md index acc5aa31..bed47dfb 100644 --- a/arboretum/technology/auditree/README.md +++ b/arboretum/technology/auditree/README.md @@ -1,26 +1,32 @@ # Auditree technology library -The fetchers and checks contained within this `auditree` technology folder are common tests that can be configured and -executed for the purpose of generating compliance reports and notifications using the [auditree-framework][]. -See [auditree-framework documentation](https://complianceascode.github.io/auditree-framework/) for more details. +The fetchers and checks contained within this `auditree` technology folder are +common tests that can be configured and executed for the purpose of generating +compliance reports and notifications using the [auditree-framework][]. They +validate the configuration and ensure smooth execution of an auditree instance. +See [auditree-framework documentation](https://complianceascode.github.io/auditree-framework/) +for more details. -These tests are normally executed by a CI/CD system like [Travis CI](https://travis-ci.com/) as part of another project -that uses this library package as a dependency. +These tests are normally executed by a CI/CD system like +[Travis CI](https://travis-ci.com/) as part of another project that uses this +library package as a dependency. ## Usage as a library -See [usage][usage] for specifics on including this library as a dependency and how to include the fetchers and checks -from this library in your downstream project. +See [usage][usage] for specifics on including this library as a dependency and +how to include the fetchers and checks from this library in your downstream project. ## Fetchers ### Abandoned Evidence * Class: [AbandonedEvidenceFetcher][fetch-abandoned-evidence] -* Purpose: Writes evidence that has been identified as abandoned to the evidence locker. -* Behavior: Stores abandoned evidence and abandoned evidence exceptions to the evidence locker. If the optional -`threshold` configuration setting is applied then abandoned evidence is identified as evidence that has not been -updated in over that `threshold` value otherwise the default is 30 days. TTL is set to 1 day. +* Purpose: Writes evidence that has been identified as abandoned to the evidence +locker. +* Behavior: Stores abandoned evidence and abandoned evidence exceptions to the +evidence locker. If the optional `threshold` configuration setting is applied +then abandoned evidence is identified as evidence that has not been updated in +over that `threshold` value otherwise the default is 30 days. TTL is set to 1 day. * Expected configuration elements: * org.auditree.abandoned\_evidence.threshold * Optional @@ -29,11 +35,12 @@ updated in over that `threshold` value otherwise the default is 30 days. TTL is * Use if looking to override the default of 30 days otherwise do not include. * org.auditree.abandoned\_evidence.exceptions * Optional - * Dictionary where the key/value pairs are the path to the evidence (key) and the reason for excluding it from the - abandoned evidence list (value). + * Dictionary where the key/value pairs are the path to the evidence (key) + and the reason for excluding it from the abandoned evidence list (value). * Key/Value: String/String - * Use if looking to exclude evidence files from being deemed abandoned and included as failures. All "exceptions" - will still appear on the report and will be treated as warnings rather than failures. + * Use if looking to exclude evidence files from being deemed abandoned and + included as failures. All "exceptions" will still appear on the report and + will be treated as warnings rather than failures. * Expected configuration (optional): ```json @@ -61,9 +68,12 @@ updated in over that `threshold` value otherwise the default is 30 days. TTL is ### Compliance Configuration * Class: [ComplianceConfigFetcher][fetch-compliance-config] -* Purpose: Writes the current configuration stored in the ComplianceConfig object to the evidence locker. -* Behavior: Stores the configuration used to execute the compliance fetchers to the evidence locker and sets a time to -live (TTL) to 2 hours. This fetcher will refresh the configuration evidence on every execution of the fetchers. +* Purpose: Writes the current configuration stored in the ComplianceConfig object +to the evidence locker. +* Behavior: Stores the configuration used to execute the compliance fetchers to +the evidence locker and sets a time to live (TTL) to 2 hours. This fetcher +ignores TTL and will refresh the configuration evidence on every execution of +the fetchers. * Expected configuration elements: * None * Expected configuration: @@ -76,24 +86,47 @@ live (TTL) to 2 hours. This fetcher will refresh the configuration evidence on from arboretum.technology.auditree.fetchers.fetch_compliance_config import ComplianceConfigFetcher ``` +### Python Packages + +* Class: [PythonPackageFetcher][fetch-python-packages] +* Purpose: Writes the current Python package dependency list to evidence. +* Behavior: Stores the current Python package dependency list as evidence and +the latest release information for `auditree-arboretum`, `auditree-framework` +and `auditree-harvest` are also retrieved and stored as evidence. The time to +live (TTL) is set to 1 day for all evidences. +* Expected configuration elements: + * None +* Expected configuration: + * None +* Expected credentials: + * None +* Import statement: + + ```python + from arboretum.technology.auditree.fetchers.fetch_python_packages import PythonPackageFetcher + ``` + ## Checks ### Abandoned Evidence * Class: [AbandonedEvidenceCheck][check-abandoned-evidence] -* Purpose: For every piece of evidence that has not been updated for longer than the time to live plus the specified -threshold a failure is generated and reported. -* Behavior: Performs a check that compares abandoned evidence identified on a given check execution with the last time new -abandoned evidence was found and reports on newly found abandoned evidence and possible exceptions. If no "abandoned -evidence" evidence is contained within the locker then this check traverses the evidence locker repository and -identifies evidence that has not been updated for a specific period of time and reports on abandoned evidence found for -the current check execution. The default threshold is 30 days beyond the time to live (TTL) setting. +* Purpose: For every piece of evidence that has not been updated for longer than +the time to live plus the specified threshold a failure is generated and reported. +* Behavior: Performs a check that compares abandoned evidence identified on a given +check execution with the last time new abandoned evidence was found and reports +on newly found abandoned evidence and possible exceptions. If no "abandoned evidence" +evidence is contained within the locker then this check traverses the evidence +locker repository and identifies evidence that has not been updated for a specific +period of time and reports on abandoned evidence found for the current check +execution. The default threshold is 30 days beyond the time to live (TTL) setting. * Evidence depended upon: * Abandoned evidence and exceptions * `raw/auditree/abandoned_evidence.json` * Gathered by the `auditree` provider [AbandonedEvidenceFetcher][fetch-abandoned-evidence] - * If the [AbandonedEvidenceFetcher][fetch-abandoned-evidence] is not used to store "abandoned evidence" evidence in - the locker then the tooling performs a sweep of the evidence locker metadata to assess evidence that has not been + * If the [AbandonedEvidenceFetcher][fetch-abandoned-evidence] is not used to + store "abandoned evidence" evidence in the locker then the tooling performs + a sweep of the evidence locker metadata to assess evidence that has not been updated in the timeframe specified. * Expected configuration elements: * org.auditree.abandoned\_evidence.threshold @@ -103,17 +136,19 @@ the current check execution. The default threshold is 30 days beyond the time t * Use if looking to override the default of 30 days otherwise do not include. * org.auditree.abandoned\_evidence.exceptions * Optional - * Dictionary where the key/value pairs are the path to the evidence (key) and the reason for excluding it from the - abandoned evidence list (value). + * Dictionary where the key/value pairs are the path to the evidence (key) + and the reason for excluding it from the abandoned evidence list (value). * Key/Value: String/String - * Use if looking to exclude evidence files from being deemed abandoned and included as failures. All "exceptions" - will still appear on the report and will be treated as warnings rather than failures. + * Use if looking to exclude evidence files from being deemed abandoned + and included as failures. All "exceptions" will still appear on the + report and will be treated as warnings rather than failures. * org.auditree.abandoned\_evidence.ignore\_history * Optional * Boolean * Set to `true` - * Use if collecting `raw/auditree/abandoned_evidence.json` in the evidence locker but intend to run the check - without referencing the evidence history (more rigid alerts). Otherwise do not include. + * Use if collecting `raw/auditree/abandoned_evidence.json` in the evidence + locker but intend to run the check without referencing the evidence history + (more rigid alerts). Otherwise do not include. * Expected configuration (optional): ```json @@ -141,10 +176,10 @@ the current check execution. The default threshold is 30 days beyond the time t ### Compliance Configuration * Class: [ComplianceConfigCheck][check-compliance-config] -* Purpose: Compare the configuration captured as evidence with the current configuration in the ComplianceConfig object -being used to execute the checks. -* Behavior: For every difference found between the evidence and the current configuration a failure is generated and -reported on. +* Purpose: Compare the configuration captured as evidence with the current +configuration in the ComplianceConfig object being used to execute the checks. +* Behavior: For every difference found between the evidence and the current +configuration a failure is generated and reported on. * Evidence depended upon: * Compliance tooling configuration settings * `raw/auditree/compliance_config.json` @@ -160,8 +195,39 @@ reported on. from auditree_central.provider.auditree.checks.test_compliance_config import ComplianceConfigCheck ``` +### Python Packages + +* Class: [PythonPackageCheck][check-python-packages] +* Purpose: Compare the most recent Python package evidence with the evidence +from the most recent historical version of that evidence found in the locker and +checks that the `auditree-arboretum`, `auditree-framework` and `auditree-harvest` +packages are at their most current release level. +* Behavior: For every difference found between the two versions of evidence a +warning is generated and reported on. Warnings are also generated when the +`auditree-arboretum`, `auditree-framework`, or `auditree-harvest` packages being +used are not at the current release version. +* Evidence depended upon: + * The executing environment's Python package list + * `raw/auditree/python_packages.json` + * `raw/auditree/auditree_arboretum_releases.xml` + * `raw/auditree/auditree_framework_releases.xml` + * `raw/auditree/auditree_harvest_releases.xml` + * Gathered by the `technology.auditree` [PythonPackageFetcher][fetch-python-packages] +* Expected configuration elements: + * None +* Expected configuration (optional): + * None + +* Import statement: + + ```python + from arboretum.technology.auditree.checks.test_python_packages import PythonPackageCheck + ``` + [usage]: https://github.com/ComplianceAsCode/auditree-arboretum#usage [fetch-abandoned-evidence]: https://github.com/ComplianceAsCode/auditree-arboretum/blob/main/arboretum/technology/auditree/fetchers/fetch_abandoned_evidence.py [fetch-compliance-config]: https://github.com/ComplianceAsCode/auditree-arboretum/blob/main/arboretum/technology/auditree/fetchers/fetch_compliance_config.py +[fetch-python-packages]: https://github.com/ComplianceAsCode/auditree-arboretum/blob/main/arboretum/technology/auditree/fetchers/fetch_python_packages.py [check-abandoned-evidence]: https://github.com/ComplianceAsCode/auditree-arboretum/blob/main/arboretum/technology/auditree/checks/test_abandoned_evidence.py [check-compliance-config]: https://github.com/ComplianceAsCode/auditree-arboretum/blob/main/arboretum/technology/auditree/checks/test_compliance_config.py +[check-python-packages]: https://github.com/ComplianceAsCode/auditree-arboretum/blob/main/arboretum/technology/auditree/checks/test_python_packages.py diff --git a/arboretum/technology/auditree/checks/test_python_packages.py b/arboretum/technology/auditree/checks/test_python_packages.py new file mode 100644 index 00000000..05a27185 --- /dev/null +++ b/arboretum/technology/auditree/checks/test_python_packages.py @@ -0,0 +1,162 @@ +# -*- mode:python; coding:utf-8 -*- +# Copyright (c) 2020 IBM Corp. All rights reserved. +# +# 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. +"""Execution environment Python packages checks.""" + +import json +from datetime import datetime, timedelta + +from compliance.check import ComplianceCheck +from compliance.evidence import DAY, ReportEvidence, with_raw_evidences + +from ..evidences.python_package_release import PackageReleaseEvidence + + +class PythonPackageCheck(ComplianceCheck): + """Compare the software versions used in Auditree execution.""" + + @property + def title(self): + """ + Return the title of the checks. + + :returns: the title of the checks + """ + return 'Python Packages' + + @classmethod + def setUpClass(cls): + """Initialize the check object with configuration settings.""" + cls.config.add_evidences( + [ + ReportEvidence( + 'python_packages.md', + 'auditree', + DAY, + 'Execution environment Python packages report.' + ) + ] + ) + + return cls + + @with_raw_evidences('auditree/python_packages.json') + def test_python_package_deltas(self, today): + """Check the last two Python environments against each other.""" + yesterday = None + yesterday_dt = datetime.utcnow() - timedelta(days=1) + try: + yesterday = self.locker.get_evidence( + 'raw/auditree/python_packages.json', + ignore_ttl=True, + evidence_dt=yesterday_dt + ) + except ValueError: + self.add_warnings( + 'Python Package Deltas', + ( + 'No evidence found on or prior ' + f'to {yesterday_dt.strftime("%b %d, %Y")}' + ) + ) + return + today_pkgs = json.loads(today.content) + yesterday_pkgs = json.loads(yesterday.content) + for pkg, ver in today_pkgs.items(): + if pkg not in yesterday_pkgs.keys(): + self.add_warnings('New Packages', f'{pkg} version {ver}') + elif ver != yesterday_pkgs[pkg]: + self.add_warnings( + 'Package Version Changes', + ( + f'{pkg} previous version {yesterday_pkgs[pkg]}, ' + f'current version {ver}' + ) + ) + for pkg in set(yesterday_pkgs.keys()) - set(today_pkgs.keys()): + self.add_warnings( + 'Removed Packages', f'{pkg} version {yesterday_pkgs[pkg]}' + ) + + @with_raw_evidences( + 'auditree/python_packages.json', + 'auditree/auditree_arboretum_releases.xml' + ) + def test_auditree_arboretum_version(self, packages, releases): + """Check auditree-arboretum version matches latest release.""" + self._test_versions(packages, releases, 'auditree-arboretum') + + @with_raw_evidences( + 'auditree/python_packages.json', + 'auditree/auditree_framework_releases.xml' + ) + def test_auditree_framework_version(self, packages, releases): + """Check auditree-framework version matches latest release.""" + self._test_versions(packages, releases, 'auditree-framework') + + def test_auditree_harvest_version(self, packages, releases): + """Check auditree-harvest version matches latest release.""" + self._test_versions(packages, releases, 'auditree-harvest') + + def _test_versions(self, packages, releases, package): + latest = PackageReleaseEvidence.from_evidence(releases).latest_release + version_used = json.loads(packages.content).get(package) + if version_used != latest: + self.add_warnings( + 'Latest Version Violation', + ( + f'{package} latest version {latest}, ' + f'version used {version_used}' + ) + ) + + def get_reports(self): + """ + Provide the check report name. + + :returns: the report(s) generated for this check + """ + return ['auditree/python_packages.md'] + + def msg_python_package_deltas(self): + """ + Python Packages deltas check notifier. + + :returns: notification dictionary + """ + return {'subtitle': 'Python package deltas', 'body': None} + + def msg_auditree_arboretum_version(self): + """ + Python Packages auditree-arboretum check notifier. + + :returns: notification dictionary + """ + return {'subtitle': 'auditree-arboretum version', 'body': None} + + def msg_auditree_framework_version(self): + """ + Python Packages auditree-framework check notifier. + + :returns: notification dictionary + """ + return {'subtitle': 'auditree-framework version', 'body': None} + + def msg_auditree_harvest_version(self): + """ + Python Packages auditree-harvest check notifier. + + :returns: notification dictionary + """ + return {'subtitle': 'auditree-harvest version', 'body': None} diff --git a/arboretum/technology/auditree/evidences/python_package_release.py b/arboretum/technology/auditree/evidences/python_package_release.py new file mode 100644 index 00000000..13af6318 --- /dev/null +++ b/arboretum/technology/auditree/evidences/python_package_release.py @@ -0,0 +1,38 @@ +# -*- mode:python; coding:utf-8 -*- +# Copyright (c) 2020 IBM Corp. All rights reserved. +# +# 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. +"""Python PyPI release evidence.""" + +from xml.etree.ElementTree import fromstring + +from compliance.evidence import RawEvidence + + +class PackageReleaseEvidence(RawEvidence): + """Python package release raw evidence class.""" + + @property + def latest_release(self): + """ + Get the latest release from the evidence. + + Gets the latest release version for the Python package represented + by the evidence. + """ + if not self.content: + return + if not hasattr(self, '_latest_release'): + root = fromstring(self.content) + self._latest_release = root[0].find('item').find('title').text + return self._latest_release diff --git a/arboretum/technology/auditree/fetchers/fetch_python_packages.py b/arboretum/technology/auditree/fetchers/fetch_python_packages.py new file mode 100644 index 00000000..e6dd3d1a --- /dev/null +++ b/arboretum/technology/auditree/fetchers/fetch_python_packages.py @@ -0,0 +1,91 @@ +# -*- mode:python; coding:utf-8 -*- +# Copyright (c) 2020 IBM Corp. All rights reserved. +# +# 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. +"""Execution environment Python packages fetchers.""" + +import json + +from arboretum.common.constants import PYPI_RSS_BASE_URL +from arboretum.technology.auditree.evidences.python_package_release import ( + PackageReleaseEvidence +) + +from compliance.evidence import DAY, RawEvidence, store_raw_evidence +from compliance.fetch import ComplianceFetcher + +from pkg_resources import working_set + + +class PythonPackageFetcher(ComplianceFetcher): + """Fetch the current environment's Python package list.""" + + @classmethod + def setUpClass(cls): + """Initialize the fetcher object with configuration settings.""" + cls.config.add_evidences( + [ + RawEvidence( + 'python_packages.json', + 'auditree', + DAY, + 'Python Package List' + ), + PackageReleaseEvidence( + 'auditree_arboretum_releases.xml', + 'auditree', + DAY, + 'Auditree Arboretum PyPI releases' + ), + PackageReleaseEvidence( + 'auditree_framework_releases.xml', + 'auditree', + DAY, + 'Auditree Framework PyPI releases' + ) + ] + ) + headers = { + 'Content-Type': 'application/xml', 'Accept': 'application/xml' + } + cls.session(PYPI_RSS_BASE_URL, **headers) + return cls + + @store_raw_evidence('auditree/python_packages.json') + def fetch_python_package_list(self): + """Fetch the Python packages in the current virtual environment.""" + packages = {} + for dist in working_set: + packages[dist.project_name] = dist.version + + return json.dumps(packages) + + @store_raw_evidence('auditree/auditree_arboretum_releases.xml') + def fetch_auditree_arboretum_releases(self): + """Fetch the auditree-arboretum package releases.""" + return self._fetch_pypi_releases('auditree-arboretum') + + @store_raw_evidence('auditree/auditree_framework_releases.xml') + def fetch_auditree_framework_releases(self): + """Fetch the auditree-framework package releases.""" + return self._fetch_pypi_releases('auditree-framework') + + @store_raw_evidence('auditree/auditree_harvest_releases.xml') + def fetch_auditree_harvest_releases(self): + """Fetch the auditree-harvest package releases.""" + return self._fetch_pypi_releases('auditree-harvest') + + def _fetch_pypi_releases(self, package_name): + resp = self.session().get(f'{package_name}/releases.xml') + resp.raise_for_status() + return resp.text diff --git a/arboretum/technology/auditree/templates/reports/auditree/abandoned_evidence.md.tmpl b/arboretum/technology/auditree/templates/reports/auditree/abandoned_evidence.md.tmpl index bb178564..b0c8aa6c 100644 --- a/arboretum/technology/auditree/templates/reports/auditree/abandoned_evidence.md.tmpl +++ b/arboretum/technology/auditree/templates/reports/auditree/abandoned_evidence.md.tmpl @@ -17,23 +17,32 @@ limitations under the License. # {{ test.title }} Report {{ now.strftime('%Y-%m-%d') }} -This report displays any evidence in the evidence locker that has not been updated in over {{ test.formatted_threshold }}. +This report displays any evidence in the evidence locker that has not been updated +in over {{ test.formatted_threshold }}. {%- if test.title == 'Latest Abandoned Evidence' -%} -This report display only changes to abandoned evidence since the last time new abandoned evidence was discovered. In other -words, when abandoned evidence is **initially** discovered it is displayed here. On subsequent executions of the abandoned -evidence check, the results are suppressed if the abandoned evidence is the same as the previous execution of the check to -reduce the "noise" in this report. +This report display only changes to abandoned evidence since the last time new +abandoned evidence was discovered. In other words, when abandoned evidence is +**initially** discovered it is displayed here. On subsequent executions of the +abandoned evidence check, the results are suppressed if the abandoned evidence +is the same as the previous execution of the check to reduce the "noise" in this +report. {% endif -%} -## Remediation +
+Remediation... -The purpose of this report is to highlight that evidence has not been refreshed in quite some time within the evidence locker. -It _could_ mean that a fetcher or check is malfunctioning or it _could_ mean that the evidence being flagged as abandoned is no -longer in use and should be considered for removal from the evidence locker. +The purpose of this report is to highlight that evidence has not been refreshed +in quite some time within the evidence locker. It _could_ mean that a fetcher +or check is malfunctioning or it _could_ mean that the evidence being flagged as +abandoned is no longer in use and should be considered for removal from the evidence +locker. Evidence can be safely removed from the evidence locker via +[auditree-prune](https://github.com/ComplianceAsCode/auditree-prune). -It is also possible to add abandoned evidence to an exceptions list in your configuration of this check/report thereby reducing -the severity of the notification. Doing this allows you to keep abandoned evidence around for a period of time while deferring -the removal of that abandoned evidence to later point in time. +It is also possible to add abandoned evidence to an exceptions list in your +configuration of this check/report thereby reducing the severity of the notification. +Doing this allows you to keep abandoned evidence around for a period of time while +deferring the removal of that abandoned evidence to later point in time. +
{% if test.total_issues_count(results) == 0 %} **No abandoned evidence to report** @@ -42,10 +51,13 @@ the removal of that abandoned evidence to later point in time. {% for section_heading, failures in all_failures.items() -%} ## {{ section_heading }} -The following evidence have not been updated in over {{ test.formatted_threshold }} and have been identified as abandoned. -Consider validating why this evidence was marked as abandoned. If it was due to a failing fetcher and check then resolve -that failure. If it was due to the evidence no longer being useful then consider [pruning the evidence from the evidence -locker](https://github.com/ComplianceAsCode/auditree-prune) or adding the evidence to the abandoned evidence exceptions list. +The following evidence have not been updated in over {{ test.formatted_threshold }} +and have been identified as abandoned. +Consider validating why this evidence was marked as abandoned. If it was due to a +failing fetcher and check then resolve that failure. If it was due to the evidence +no longer being useful then consider [pruning the evidence from the evidence +locker](https://github.com/ComplianceAsCode/auditree-prune) or adding the evidence +to the abandoned evidence exceptions list. {% for failure in failures %} - [`{{ failure['ae_path'] }}`]({{ test.config.get('locker.repo_url') }}/blob/master/{{ failure['ae_path'] }}) - **Last updated:** {{ failure['last_update'] }} @@ -57,8 +69,9 @@ locker](https://github.com/ComplianceAsCode/auditree-prune) or adding the eviden {% for section_heading, warnings in all_warnings.items() %} ## {{ section_heading }} -The following evidence have not been updated in over {{ test.formatted_threshold }} and have been identified as abandoned -evidence exceptions. Have a look at the reason for an evidence being included on the exceptions list and consider whether +The following evidence have not been updated in over {{ test.formatted_threshold }} +and have been identified as abandoned evidence exceptions. Have a look at the +reason for an evidence being included on the exceptions list and consider whether it makes sense to [prune that evidence from the evidence locker](https://github.com/ComplianceAsCode/auditree-prune). {% for warning in warnings %} diff --git a/arboretum/technology/auditree/templates/reports/auditree/compliance_config.md.tmpl b/arboretum/technology/auditree/templates/reports/auditree/compliance_config.md.tmpl index 779f96d2..bd575612 100644 --- a/arboretum/technology/auditree/templates/reports/auditree/compliance_config.md.tmpl +++ b/arboretum/technology/auditree/templates/reports/auditree/compliance_config.md.tmpl @@ -18,13 +18,19 @@ limitations under the License. # {{ test.title }} Report {{ now.strftime('%Y-%m-%d') }} This report displays differences between the configuration used by the fetcher -execution and the configuration used by the check execution. Fetchers and checks -are executed as separate processes but the expectation is that both processes -are executed using the same configuration settings as defined by a project's -configuration JSON file. If discrepancies are found and reported here then a -serious problem occurred during execution. +execution and the configuration used by the check execution. -## Remediation +
+More details... + +Fetchers and checks are executed as separate processes but the expectation is +that both processes are executed using the same configuration settings as defined +by a project's configuration JSON file. If discrepancies are found and reported +here then a serious problem occurred during execution. +
+ +
+Remediation... All discrepancies reported here need to be fully investigated and accounted for. Since the official execution of fetchers and checks is handled by a CI/CD tool @@ -32,6 +38,7 @@ like Travis, the execution environment should be a closed "eco-system" and there should be no reason for a difference in execution configuration between the fetchers and the checks. In the event of a problem reported here, check your CI/CD build logs for more details. +
{% if test.total_issues_count(results) == 0 %} **No execution configuration discrepancies to report** diff --git a/arboretum/technology/auditree/templates/reports/auditree/python_packages.md.tmpl b/arboretum/technology/auditree/templates/reports/auditree/python_packages.md.tmpl new file mode 100644 index 00000000..d3d29235 --- /dev/null +++ b/arboretum/technology/auditree/templates/reports/auditree/python_packages.md.tmpl @@ -0,0 +1,71 @@ +{#- -*- mode:jinja2; coding: utf-8 -*- -#} +{# +Copyright (c) 2020 IBM Corp. All rights reserved. + +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. +#} + +# {{ test.title }} Report {{ now.strftime('%Y-%m-%d') }} + +This report displays differences in the compliance automation execution Python +package environment since the previous execution. + +
+More details... + +Any **package version changes** +are largely informational. They _can_ be used to debug why things worked +previously but aren't working today. The compliance automation framework execution +environment depends on other of Python libraries. These libraries can have new +releases and it's not outside the realm of possibility that a new release of a +dependency could cause a problem. This report provides information that helps to +guard against that. This report also checks whether the versions of the +[auditree-arboretum](https://github.com/ComplianceAsCode/auditree-arboretum), +the [auditree-framework](https://github.com/ComplianceAsCode/auditree-framework) +and the [auditree-harvest](https://github.com/ComplianceAsCode/auditree-harvest) +packages are the most recent versions available, as is the expected behavior. +
+ +
+Remediation... + +Package version change warnings are informational but can be used in part +to debug why things worked previously but currently don't. However, if the +`auditree-arboretum`, `auditree-framework`, or the `auditree-harvest` packages +are flagged as a **latest version violation** then that needs to be explained. +It is expected that the most recent versions of each of those packages are used +during fetcher, check and report execution. +
+ +{% if test.total_issues_count(results) == 0 -%} +**No python package version differences to report** +{% else -%} +{% if test.warnings_for_check_count(results) > 0 -%} +{% for warning, instances in all_warnings.items() %} + +## {{ warning }} +{% if warning == 'Latest Version Violation'-%} +Checks that ensure that the `auditree-arboretum`, `auditree-framework` and +`auditree-harvest` packages are up to date have uncovered issues. These warnings +should be investigated because the expectation is that these packages should be +current and up to date. +{% else -%} +These findings are largely informational. They can be used to debug issues with +the execution environment. +{% endif -%} +{% for instance in instances %} +* {{ instance }} +{%- endfor -%} +{%- endfor -%} +{%- endif -%} +{%- endif -%} diff --git a/controls.json b/controls.json index 4c7faf97..8fd1188b 100644 --- a/controls.json +++ b/controls.json @@ -8,5 +8,10 @@ "auditree_evidence": { "auditree_control": ["tech.auditree"] } + }, + "arboretum.technology.auditree.checks.test_python_packages.PythonPackageCheck": { + "auditree_evidence": { + "auditree_control": ["tech.auditree"] + } } } diff --git a/test/fixtures/pypi_release_info.xml b/test/fixtures/pypi_release_info.xml new file mode 100644 index 00000000..980d0314 --- /dev/null +++ b/test/fixtures/pypi_release_info.xml @@ -0,0 +1,45 @@ + + + + + PyPI recent updates for auditree-framework + https://pypi.org/project/auditree-framework/ + Recent updates to the Python Package Index for auditree-framework + en + + 1.0.2 + https://pypi.org/project/auditree-framework/1.0.2/ + Tool to run compliance control checks as unit tests + al.finkelstein@ibm.com + Tue, 28 Jul 2020 20:48:48 GMT + + + 1.0.1 + https://pypi.org/project/auditree-framework/1.0.1/ + Tool to run compliance control checks as unit tests + al.finkelstein@ibm.com + Mon, 27 Jul 2020 17:30:15 GMT + + + 1.0.0 + https://pypi.org/project/auditree-framework/1.0.0/ + Tool to run compliance control checks as unit tests + al.finkelstein@ibm.com + Tue, 21 Jul 2020 15:38:18 GMT + + + diff --git a/test/test_evidences/__init__.py b/test/test_evidences/__init__.py new file mode 100644 index 00000000..1ec168b2 --- /dev/null +++ b/test/test_evidences/__init__.py @@ -0,0 +1,15 @@ +# -*- mode:python; coding:utf-8 -*- +# Copyright (c) 2020 IBM Corp. All rights reserved. +# +# 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. +"""Arboretum evidence helpers unit tests.""" diff --git a/test/test_evidences/test_python_package_release.py b/test/test_evidences/test_python_package_release.py new file mode 100644 index 00000000..e7f83377 --- /dev/null +++ b/test/test_evidences/test_python_package_release.py @@ -0,0 +1,47 @@ +# -*- mode:python; coding:utf-8 -*- +# Copyright (c) 2020 IBM Corp. All rights reserved. +# +# 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. +"""Python package release evidence unit tests.""" + +import unittest + +from arboretum.technology.auditree.evidences.python_package_release import ( + PackageReleaseEvidence +) + + +class PythonPackageReleaseTest(unittest.TestCase): + """PythonPackageRelease unit tests.""" + + def test_latest_release_success(self): + """Ensure that latest available release is returned.""" + evidence = PackageReleaseEvidence('foo.xml', 'bar') + evidence.set_content( + open('./test/fixtures/pypi_release_info.xml').read() + ) + self.assertEqual(evidence.latest_release, '1.0.2') + + def test_latest_release_none(self): + """Ensure latest release is None if no content.""" + evidence = PackageReleaseEvidence('foo.xml', 'bar') + self.assertIsNone(evidence.latest_release) + + def test_latest_release_already_set(self): + """Ensure that latest release only retrieved if not yet set.""" + evidence = PackageReleaseEvidence('foo.xml', 'bar') + evidence._latest_release = 'foo.bar.baz' + evidence.set_content( + open('./test/fixtures/pypi_release_info.xml').read() + ) + self.assertEqual(evidence.latest_release, 'foo.bar.baz')