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)