Skip to content

Commit

Permalink
Access Control Support for HTTP APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
ericpassmore committed Apr 19, 2024
1 parent 2c49e59 commit cf31833
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 26 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@ Select `LowEndOrchestrator` and use the default template.
![OrchTemplaceSelect](docs/images/CDOrchTemplateSelect.png)

## Configuring OAuth
Authentication and Access control is managed through an OAuth to GitHub. Starting the system for the first time requires a file named `env` in current working directory. An example `env.development` is provided that you may copy, and update to match the `secret`, `client_id`, and `callback_url` of your OAuth app. If the `env` file is not present, the application will not start, and it will emit the error `Can't find file env in current directory, not able to parse env properties, exiting.`
Authentication and Access control is managed through an OAuth to GitHub. Starting the system for the first time requires a file named `env` in current working directory. An example `env.development` is provided that you may copy, and update to match the `secret`, `client_id`, and `callback_url` of your OAuth app.

If no `env` file is present in the working directory, the AWS User Data script will create one, in the home directory, using the contents of `env.defaults`. The default configuration is not correct, and OAuth will fail. Using the default configuration will allow the application to start, and respond to healthchecks. Please make sure to review the `env` file if you have any issues with authentication.
If the `env` file is not present, the application will not start, and it will emit the error `Can't find file env in current directory, not able to parse env properties, exiting.` If no `env` file is present in the working directory, when you deploy a new orchestration instances in AWS, the AWS User Data script will create one, in the home directory, using the contents of `env.defaults`. The default configuration is not correct, and OAuth will fail. Using the default configuration will allow the application to start, and respond to healthchecks. Please make sure to review the `env` file if you have any issues with authentication.

### Access Control
To gain access to the application, a user must have write access to specific GitHub repositories. These repositories are also found in the `env` file.
To gain access to the application, a user must have membership in specific GitHub teams. The org and teams checked for membership are found in the `env` file. You may use multiple teams for access control by providing a comma separated list in the `env` file. Access to the application is checked on every HTTP request, and the application makes HTTP calls to GitHub to ensure the user has sufficient privileges to perform the requested action. There are two methods of access control:
- Using a web browser via OAuth: Click on the person icon in the top right corner to login. You will be redirected to GitHub to authenticate.
- Using HTTP command line: Pass the header `Authorization` with your valid GitHub token. The GitHub token must have `read:org` scope for the organization specified in the `env` file.

Example of command line access
```
curl -H 'Accept: application/json' -H 'Authorization: gho_bBB1bB1BBbbBbb1BBbBbBB1bbbb1BbbBB' http://127.0.0.1:4000/status
```

## Updating Orchestrator Job Configuration
By default the setup will spin up a webservice with [Production Run from Jan 2024](meta-data/full-production-run-20240101.json). To change the job configuration you need to create your own JSON configuration, and restart the service to use the new JSON. **Note** need to use `nohup` on python webservice to keep the process running after ssh-shell exit.
Expand Down
2 changes: 1 addition & 1 deletion env.development
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ authorize_url=https://github.com/login/oauth/authorize
registered_callback=https://example.com/oauthback
access_token=https://github.com/login/oauth/access_token
user_info_url=https://api.github.com/user
team=ORG/TEAM
team=ORG/TEAM_1, ORG/TEAM_2
38 changes: 21 additions & 17 deletions orchestration-service/github_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,25 @@ def check_membership(bearer_token, login, team_string):
"""Check for team membership"""
if not login:
return False
org, team = team_string.split('/',1)
url = f'https://api.github.com/orgs/{org}/teams/{team}/members'
membership_check = requests.get(url,
timeout=3,
headers={
'Accept': 'application/vnd.github+json',
'Authorization': f'Bearer {bearer_token}',
'X-GitHub-Api-Version': '2022-11-28',
'User-Agent': 'App/OAuth/ReplayTest'
})
if membership_check.status_code == 200:
members_list = json.loads(membership_check.content.decode('utf-8'))
for member in members_list:
if member['login'] == login:
return True
# many contain many teams
for unit in team_string.split(','):
org, team = unit.split('/',1)
org = org.strip()
team = team.strip()
url = f'https://api.github.com/orgs/{org}/teams/{team}/members'
membership_check = requests.get(url,
timeout=3,
headers={
'Accept': 'application/vnd.github+json',
'Authorization': f'Bearer {bearer_token}',
'X-GitHub-Api-Version': '2022-11-28',
'User-Agent': 'App/OAuth/ReplayTest'
})
if membership_check.status_code == 200:
members_list = json.loads(membership_check.content.decode('utf-8'))
for member in members_list:
if member['login'] == login:
return True
return False

@staticmethod
Expand All @@ -85,8 +89,8 @@ def is_authorized(cookies, header_token, user_info_url, team_string):
token = None
if 'replay_auth' in cookies and cookies['replay_auth']:
token = GitHubOauth.extract_token(cookies['replay_auth'])
if header_token:
token = header_token
elif header_token:
token = header_token.replace("Bearer ","")
if not token:
return False

Expand Down
15 changes: 10 additions & 5 deletions orchestration-service/web_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,15 @@ def application(request):
ETag {request.headers.get('ETag')}""")

# auth check /progress /grid /control /detail are HTML pages
# healthcheck does not require auth
# /healthcheck does not require acess control
# /oauthback is called before access control is avalible
# they have their own auth flow and messages, so we skip them for out auth check
# this protects API calls
if request.path not in ['/progress', '/grid', '/control', '/detail', '/healthcheck', '/oauthback'] and \
not (ALWAYS_ALLOW or GitHubOauth.is_authorized(request.cookies, None,
env_name_values.get('user_info_url'), env_name_values.get('team'))):
not (ALWAYS_ALLOW or GitHubOauth.is_authorized(request.cookies,
request.headers.get('Authorization'),
env_name_values.get('user_info_url'),
env_name_values.get('team'))):
return Response("Not Authorized", status=403)

if request.path == '/job':
Expand Down Expand Up @@ -254,8 +257,10 @@ def application(request):
referring_url = request.path

if ALWAYS_ALLOW or \
GitHubOauth.is_authorized(request.cookies, None,
env_name_values.get('user_info_url'), env_name_values.get('team')):
GitHubOauth.is_authorized(request.cookies,
request.headers.get('Authorization'),
env_name_values.get('user_info_url'),
env_name_values.get('team')):
# Retrieve the auth cookie
cookie_value = request.cookies.get('replay_auth')
login, avatar_url = GitHubOauth.str_to_public_profile(cookie_value)
Expand Down

0 comments on commit cf31833

Please sign in to comment.