Skip to content

Commit

Permalink
Add organization direct collaborators check (#47)
Browse files Browse the repository at this point in the history
* Add organization direct collaborators check
  • Loading branch information
mlomena authored Feb 15, 2021
1 parent 1b63ab3 commit ed3cdfc
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# [0.10.0](https://github.com/ComplianceAsCode/auditree-arboretum/releases/tag/v0.10.0)

- [ADDED] Organization repository direct collaborators check added to `permissions`.

# [0.9.0](https://github.com/ComplianceAsCode/auditree-arboretum/releases/tag/v0.9.0)

- [ADDED] Kubernetes resources fetcher added.
Expand Down
2 changes: 1 addition & 1 deletion arboretum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
# limitations under the License.
"""Arboretum - Checking your compliance & security posture, continuously."""

__version__ = '0.9.0'
__version__ = '0.10.0'
67 changes: 65 additions & 2 deletions arboretum/permissions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,73 @@ day.

## Checks

Checks coming soon...
### Organization Integrity (Repository Collaborators)

* Class: [OrgCollaboratorsCheck][org-collaborators-check]
* Purpose: Ensure that `direct` collaborators do not exist in the organization repositories.
* Behavior: Collaborators are checked for every repository.
A failure is generated when direct collaborators are found in a repository. This check can be optionally
configured to accept exceptions, a warning instead of a failure is generated for those exceptions when
direct collaborators matching the exceptions are found.
* Evidence depended upon:
* `direct` collaborators found in organization repositories.
* `raw/permissions/<gh|gl|bb>_direct_collaborators_<org_url_hash>.json`
* NOTE: Only gh (Github) is currently supported by this check. Gitlab and Bitbucket support coming soon...
* Configuration elements:
* `org.permissions.org_integrity.orgs`
* Required
* List of dictionaries:
* `url`
* Required
* Organization URL (string).
* `exceptions`
* Optional
* List of dictionaries:
* `user`
* Required
* Github, Gitlab or Bitbucket user id (string).
* Use to define the user to be treated as an exception.
* NOTE: Only Github is currently supported by this check. Gitlab and Bitbucket support coming soon...
* `repos`
* Optional
* List of strings in the form of `["repo_a", "repo_b"]`.
* Defaults to all repositories in the organization.
* Use to limit the user exception to specific repositories in the organization.
* Example configuration:

```json
{
"org": {
"permissions": {
"org_integrity": {
"orgs": [
{
"url": "https://github.com/my-org-1",
"collaborator_types": ["direct"],
"exceptions": [
{
"user": "userid_1"
},
{
"user": "userid_2",
"repos": ["repo_a", "repo_b"]
}
]
}
]
}
}
}
}
```
* Import statement:
```python
from arboretum.permissions.checks.test_org_collaborators import OrgCollaboratorsCheck
```

[auditree-framework]: https://github.com/ComplianceAsCode/auditree-framework
[auditree-framework documentation]: https://complianceascode.github.io/auditree-framework/
[usage]: https://github.com/ComplianceAsCode/auditree-arboretum#usage
[gh-org-fetcher]: https://github.com/ComplianceAsCode/auditree-arboretum/blob/main/arboretum/permissions/fetchers/github/fetch_org_collaborators.py
[repository-permissions]: https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-organizations-and-teams/repository-permission-levels-for-an-organization
[repository-permissions]: https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-organizations-and-teams/repository-permission-levels-for-an-organization
[org-collaborators-check]: https://github.com/ComplianceAsCode/auditree-arboretum/blob/main/arboretum/permissions/checks/test_org_collaborators.py
114 changes: 114 additions & 0 deletions arboretum/permissions/checks/test_org_collaborators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# -*- mode:python; coding:utf-8 -*-
# Copyright (c) 2021 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.
"""Repository organization/owner collaborators check."""

from compliance.check import ComplianceCheck
from compliance.evidence import DAY, ReportEvidence, evidences
from compliance.utils.data_parse import get_sha256_hash


class OrgCollaboratorsCheck(ComplianceCheck):
"""Checks for repository organization/owner collaborators."""

@property
def title(self):
"""
Return the title of the checks.
:returns: the title of the checks
"""
return 'Repository Organization/Owner Collaborators'

@classmethod
def setUpClass(cls):
"""Initialize the check object with configuration settings."""
cls.config.add_evidences(
[
ReportEvidence(
'org_collaborators.md',
'permissions',
DAY,
'Repository organization collaborators report.'
)
]
)
return cls

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)

def _generate_results(self, evidences, exceptions):
for org, ev in evidences.items():
for repo in ev.content_as_json:
all_users = [u['login'] for u in ev.content_as_json[repo]]
if not all_users:
continue
exception_users = [
e['user']
for e in exceptions[org]
if 'repos' not in e.keys() or repo in e['repos']
]
failed_users = set(all_users) - set(exception_users)
warning_users = set(all_users).intersection(exception_users)
if failed_users:
self.add_failures(
'unexpected-org-collaborators', {
'org': org, 'repo': repo, 'users': failed_users
}
)
if warning_users:
self.add_warnings(
'allowed-org-collaborators', {
'org': org, 'repo': repo, 'users': warning_users
}
)

def get_notification_message(self):
"""
Repository organization collaborators check notifier.
:returns: notification dictionary
"""
return {
'subtitle': 'Repository organization collaborators', 'body': None
}

def get_reports(self):
"""
Provide the check report name.
:returns: the report(s) generated for this check
"""
return ['permissions/org_collaborators.md']
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{#- -*- mode:jinja2; coding: utf-8 -*- -#}
# {{ test.title }} for {{ now.strftime('%Y-%m-%d') }}

This report flags direct collaborator access found in the organization repositories.

Organizations and repositories checked:

| Organization | Repositories |
| ---------------------- | ------------------------- |
{%- for org in test.config.get('org.permissions.org_integrity.orgs') -%}
{%- if 'collaborator_types' in org.keys() and 'direct' in org['collaborator_types'] %}
| {{ org['url'].rsplit('/', 1)[1] }} | {{ ', '.join(org['repos']) if 'repos' in org.keys() else 'All repositories' }} |
{%- endif %}
{%- endfor %}

It issues failures when:

- Users have organization repository access as direct collaborators.

It issues warnings when:

- Direct collaborators are found but are listed as exceptions in the configuration.

If a failure or warning is not present for the above category of check this
means that the check passed without issue.

{% if (all_failures | length == 0) and (all_warnings | length == 0) %}
No issues found.
{%- else -%}

{% for category, failures in all_failures.items() %}

{% if category == 'unexpected-org-collaborators' %}
## Failure: Users found in organizations as direct repo collaborators

| User | Organization | Repository |
| ---------------------- | ------------------------- | ------------------------- |
{%- for failure in failures %}
{%- for user in failure['users'] %}
| {{ user }} | {{ failure['org'] }} | {{ failure['repo'] }} |
{%- endfor -%}
{%- endfor -%}
{%- endif -%}
{%- endfor -%}

{% for category, warnings in all_warnings.items() %}
{% if category == 'allowed-org-collaborators' %}
## Warning: Users found in organizations as direct repo collaborators but allowed as exceptions

| User | Organization | Repository |
| ---------------------- | ------------------------- | ------------------------- |
{%- for warning in warnings %}
{%- for user in warning['users'] %}
| {{ user }} | {{ warning['org'] }} | {{ warning['repo'] }} |
{%- endfor -%}
{%- endfor -%}
{%- endif -%}
{%- endfor -%}
{%- endif -%}

0 comments on commit ed3cdfc

Please sign in to comment.