def test_branch_protection_integrity(self):
     """Check whether branch protection settings are set for admins."""
     locker_branches = self.config.get(
         'org.auditree.locker_integrity.branches',
         self.config.get('org.auditree.repo_integrity.branches',
                         {self.config.get('locker.repo_url'): ['master']}))
     for locker_url, branches in locker_branches.items():
         parsed = urlparse(locker_url)
         service = 'gh'
         if 'gitlab' in parsed.hostname:
             service = 'gl'
         elif 'bitbucket' in parsed.hostname:
             service = 'bb'
         repo = parsed.path.strip('/')
         for branch in branches:
             filename = [
                 service,
                 repo.lower().replace('/', '_').replace('-', '_'),
                 branch.lower().replace('-', '_'), 'branch_protection.json'
             ]
             path = f'raw/auditree/{"_".join(filename)}'
             with evidences(self, path) as raw:
                 evidence = RepoBranchProtectionEvidence.from_evidence(raw)
                 if not evidence.admin_enforce:
                     self.add_failures(
                         'Locker Branch Protection',
                         (f'Branch protection for `{locker_url}` '
                          f'`{branch}` branch '
                          'is not enforced for administrators.'))
 def test_new_repo_branch_commits(self):
     """Check for new commits made to a repo/branch."""
     branches = self.config.get('org.auditree.repo_integrity.branches')
     for repo_url, repo_branches in branches.items():
         parsed = urlparse(repo_url)
         service = 'gh'
         if 'gitlab' in parsed.hostname:
             service = 'gl'
         elif 'bitbucket' in parsed.hostname:
             service = 'bb'
         repo = parsed.path.strip('/')
         for repo_branch in repo_branches:
             # If included, skip check on the evidence locker
             if (repo_url == self.locker.repo_url
                     and repo_branch == self.locker.branch):
                 continue
             filename = [
                 service,
                 repo.lower().replace('/', '_').replace('-', '_'),
                 repo_branch.lower().replace('-', '_'),
                 'recent_commits.json'
             ]
             path = f'raw/auditree/{"_".join(filename)}'
             with evidences(self, path) as raw:
                 commits = RepoCommitEvidence.from_evidence(raw)
                 for commit in commits.author_info:
                     commit['repo'] = repo_url
                     commit['branch'] = repo_branch
                     self.add_warnings('Recent Commits Found', commit)
 def test_branch_protection_commit_integrity(self):
     """Check that branch protection requires signed commits."""
     locker_branches = self.config.get(
         'org.auditree.locker_integrity.branches',
         self.config.get('org.auditree.repo_integrity.branches',
                         {self.config.get('locker.repo_url'): ['master']}))
     for locker_url, branches in locker_branches.items():
         parsed = urlparse(locker_url)
         service = 'gh'
         if 'gitlab' in parsed.hostname:
             service = 'gl'
         elif 'bitbucket' in parsed.hostname:
             service = 'bb'
         repo = parsed.path.strip('/')
         for branch in branches:
             filename = [
                 service,
                 repo.lower().replace('/', '_').replace('-', '_'),
                 branch.lower().replace('-', '_'), 'branch_protection.json'
             ]
             path = f'raw/auditree/{"_".join(filename)}'
             with evidences(self, path) as raw:
                 evidence = RepoBranchProtectionEvidence.from_evidence(raw)
                 if not evidence.signed_commits_required:
                     self.add_failures(('Locker Branch Protection - '
                                        '(Signed Commits Disabled)'),
                                       f'`{locker_url}` `{branch}` branch.')
 def test_recent_commit_integrity(self):
     """Check that recent commits are signed."""
     locker_branches = self.config.get(
         'org.auditree.locker_integrity.branches',
         self.config.get('org.auditree.repo_integrity.branches',
                         {self.config.get('locker.repo_url'): ['master']}))
     for locker_url, branches in locker_branches.items():
         parsed = urlparse(locker_url)
         service = 'gh'
         if 'gitlab' in parsed.hostname:
             service = 'gl'
         elif 'bitbucket' in parsed.hostname:
             service = 'bb'
         repo = parsed.path.strip('/')
         for branch in branches:
             filename = [
                 service,
                 repo.lower().replace('/', '_').replace('-', '_'),
                 branch.lower().replace('-', '_'), 'recent_commits.json'
             ]
             path = f'raw/auditree/{"_".join(filename)}'
             with evidences(self, path) as raw:
                 commits = RepoCommitEvidence.from_evidence(raw)
                 for commit in commits.signed_status:
                     if not commit['signed']:
                         self.add_failures(
                             'Locker Recent Commits - (Unsigned)',
                             (f'[{commit["sha"][:8]}]({commit["url"]}) '
                              f'commit in `{locker_url}` '
                              f'`{branch}` branch.'))
 def test_new_filepath_commits(self):
     """Check for new commits made to a repo/branch/filepath."""
     filepaths = self.config.get('org.auditree.repo_integrity.filepaths')
     for repo_url, repo_branches in filepaths.items():
         parsed = urlparse(repo_url)
         service = 'gh'
         if 'gitlab' in parsed.hostname:
             service = 'gl'
         elif 'bitbucket' in parsed.hostname:
             service = 'bb'
         repo = parsed.path.strip('/')
         for repo_branch, repo_filepaths in repo_branches.items():
             for filepath in repo_filepaths:
                 ev_file_prefix = f'{repo}_{repo_branch}_{filepath}'.lower()
                 for symbol in [' ', '/', '-', '.']:
                     ev_file_prefix = ev_file_prefix.replace(symbol, '_')
                 fname = f'{service}_{ev_file_prefix}_recent_commits.json'
                 with evidences(self, f'raw/auditree/{fname}') as raw:
                     commits = RepoCommitEvidence.from_evidence(raw)
                     for commit in commits.author_info:
                         commit['repo'] = repo_url
                         commit['branch'] = repo_branch
                         self.add_warnings(
                             f'Recent Commits Found - (`{filepath}`)',
                             commit)
 def test_metadata_integrity(self):
     """Check whether the repo details have unexpectedly changed."""
     locker_urls = self.config.get(
         'org.auditree.locker_integrity.repos',
         self.config.get('org.auditree.repo_integrity.repos',
                         [self.config.get('locker.repo_url')]))
     for locker_url in locker_urls:
         parsed = urlparse(locker_url)
         service = 'gh'
         if 'gitlab' in parsed.hostname:
             service = 'gl'
         elif 'bitbucket' in parsed.hostname:
             service = 'bb'
         repo = parsed.path.strip('/')
         filename = [
             service,
             repo.lower().replace('/', '_').replace('-', '_'),
             'repo_metadata.json'
         ]
         path = f'raw/auditree/{"_".join(filename)}'
         with evidences(self, path) as raw:
             evidence_found = True
             previous_dt = datetime.utcnow() - timedelta(days=1)
             try:
                 previous_raw = self.get_historical_evidence(
                     path, previous_dt)
             except ValueError:
                 self.add_failures(
                     'Locker Repository Metadata - (No prior evidence)',
                     ('No prior evidence found on or prior '
                      f'to {previous_dt.strftime("%b %d, %Y")} '
                      f'for locker `{locker_url}`.'))
                 evidence_found = False
             if evidence_found:
                 current = RepoMetadataEvidence.from_evidence(raw)
                 prev = RepoMetadataEvidence.from_evidence(previous_raw)
                 if current.repo_size < prev.repo_size:
                     self.add_warnings(
                         'Locker Repository Metadata - (Locker shrunk)',
                         (f'Locker `{locker_url}` appears to have '
                          'shrunk in size/content.  It was '
                          f'{str(prev.repo_size)} and is '
                          f'now {str(current.repo_size)}.'))
                 difference = ''.join(
                     context_diff(
                         prev.filtered_content.splitlines(keepends=True),
                         current.filtered_content.splitlines(keepends=True),
                         path, path, previous_dt.strftime('%b %d, %Y'),
                         datetime.utcnow().strftime('%b %d, %Y')))
                 if difference:
                     self.add_failures(
                         'Locker Repository Metadata - (Metadata changed)',
                         (f'Locker `{locker_url}` details have changed.'
                          f'\n\n```\n{difference}\n```\n'))
    def test_org_direct_collaborators(self):
        """Check that there are no direct collaborators in the org repos."""
        orgs = self.config.get('org.permissions.org_integrity.orgs')
        evidence_paths = {}
        exceptions = {}
        for org in orgs:
            if 'direct' not in org.get('collaborator_types', []):
                continue
            host, org_name = org['url'].rsplit('/', 1)
            service = 'gh'
            if 'gitlab' in host:
                service = 'gl'
            elif 'bitbucket' in host:
                service = 'bb'

            url_hash = get_sha256_hash([org['url']], 10)
            filename = f'{service}_direct_collaborators_{url_hash}.json'
            path = f'raw/permissions/{filename}'
            evidence_paths[org_name] = path
            exceptions[org_name] = org.get('exceptions', [])
        with evidences(self, evidence_paths) as raws:
            self._generate_results(raws, exceptions)