targets: https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile
https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=https://developers.google.com/oauthplayground&prompt=consent&response_type=code&client_id=407408718192.apps.googleusercontent.com&scope=https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile&access_type=offline send the returned token to the application to request a token request:
// Request
POST /oauth2/v4/token HTTP/1.1
Host: www.googleapis.com
Content-length: 277
content-type: application/x-www-form-urlencoded
user-agent: google-oauth-playground
code=4%2FAAC8yZdfe98kiiX6R-lojj5Dqe4HHtGkDPMlaFobNBn1ai4aHpmRhpyZvD49xxsyHJaD5of_1zf50I7KyMZeXyk&redirect_uri=https%3A%2F%2Fdevelopers.google.com%2Foauthplayground&client_id=407408718192.apps.googleusercontent.com&client_secret=************&scope=&grant_type=authorization_code
// Response
HTTP/1.1 200 OK
Content-length: 1389
X-xss-protection: 1; mode=block
X-content-type-options: nosniff
Transfer-encoding: chunked
Expires: Mon, 01 Jan 1990 00:00:00 GMT
Vary: Origin, X-Origin
Server: GSE
-content-encoding: gzip
Pragma: no-cache
Cache-control: no-cache, no-store, max-age=0, must-revalidate
Date: Sun, 22 Jul 2018 16:26:54 GMT
X-frame-options: SAMEORIGIN
Alt-svc: quic=":443"; ma=2592000; v="44,43,39,35"
Content-type: application/json; charset=UTF-8
{
// This is unique to the application
"access_token": "ya29.GlsABqo0h39HIGVZS3nJ_ZxU73fIfK929JGLXve2CCFJXv89HNyGY-tJZAONtfJCo1nktLYMXENSfvmZKhPehprQki1ZmluG6iHNTWwSEyHTg8NIfSmDavrH9hQd",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1/w2Ix95nfsB8x7dc_og4PhtvqxKKs10t2LW4-xvoZmxU",
// This is unique to the user
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImM2YWY3Y2FhMDg5NWZkMDFlNzc4ZGNlYWE3YTc5ODgzNDdkOGYyNWMifQ.eyJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDA3NDA4MTcxMjQ4NjUwNDA2MTgiLCJoZCI6InVpYy5lZHUiLCJlbWFpbCI6ImtkaXhsZTJAdWljLmVkdSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiengxT2dGeURTVV9JMlRLRl9WenRBUSIsImV4cCI6MTUzMjI4MDQxNCwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwiaWF0IjoxNTMyMjc2ODE0LCJuYW1lIjoiS3lsZSBEaXhsZXIiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDUuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1CZnZfY3NOUWRHWS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BQW5uWTdyUWZfVmE5NDFpZnZYMFMxdFViYl9qU2x6OVBRL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJLeWxlIiwiZmFtaWx5X25hbWUiOiJEaXhsZXIiLCJsb2NhbGUiOiJlbiJ9.H9LJ8do7VqUepbYI4t2Mr1yrvTQJYvUnoy7XGdbXTdKUhHYG7VKpoWd76MkT77oujXQhpMJ60c_BYhCo8EpxufM75gYhVWhak4L1MOWIeHKwJFfRxu9kWVsgBtsAwsUKe4J4ZswA06bLQP10i9GB1Fjo0bimRaTfFyWTDdO_UfpMVw1QvsdPFUqhTGAmqHY3dBU3sNBMZcI3LtFCUVAlCR3-YFDsOvklyhRrwNV_oqDh6HoCL4IJGViI6LMpAdSJjNElFzitlEXUV8ET2Fw1FsAxs-PPKNmYVgI1119yYKmmm_KQJoZI5vwIZtzZKdzh6pnV7SRlWtcKASh-ynr0lg"
}
Now we can use the token to request user data. The token may time out, but we can use it on our end. We use the credential to retrieve user data:
- email(uic email)
// Request
GET /oauth2/v2/userinfo HTTP/1.1
Host: www.googleapis.com
Content-length: 0
Authorization: Bearer ya29.glsabqo0h39higvzs3nj_zxu73fifk929jglxve2ccfrxv89hnyly-tjzaontfjco1nktlymxensfvmzkhpehprqkb1zmlug6ihntwwseyhtg8nifsmdavrh9hqd
// Response
HTTP/1.1 200 OK
Content-length: 331
X-xss-protection: 1; mode=block
Content-location: https://www.googleapis.com/oauth2/v2/userinfo
X-content-type-options: nosniff
Transfer-encoding: chunked
Expires: Mon, 01 Jan 1990 00:00:00 GMT
Vary: Origin, X-Origin
Server: GSE
-content-encoding: gzip
Pragma: no-cache
Cache-control: no-cache, no-store, max-age=0, must-revalidate
Date: Sun, 22 Jul 2018 16:29:04 GMT
X-frame-options: SAMEORIGIN
Alt-svc: quic=":443"; ma=2592000; v="44,43,39,35"
Content-type: application/json; charset=UTF-8
{
"family_name": "Dixler",
"name": "Kyle Dixler",
"picture": "https://lh5.googleusercontent.com/-Bfv_csNQdGY/AAAAAAAAAAI/AAAAAAAAAAA/AAnnY7rQf_Va941ifvX0S1tUbb_jSlz9PQ/mo/photo.jpg",
"locale": "en",
"email": "kdixle2@uic.edu",
"given_name": "Kyle",
"id": "000000000000000000000",
"hd": "uic.edu",
"verified_email": true
}
Then we can store the cookie into our tokens-table and return it for our user to put in localStorage and to use as their token
Stores the session credentials of our users put a lifecycle policy for these tokens
//"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImM2YWY3Y2FhMDg5NWZkMDFlNzc4ZGNlYWE3YTc5ODgzNDdkOGYyNWMifQ.eyJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDA3NDA4MTcxMjQ4NjUwNDA2MTgiLCJoZCI6InVpYy5lZHUiLCJlbWFpbCI6ImtkaXhsZTJAdWljLmVkdSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiengxT2dGeURTVV9JMlRLRl9WenRBUSIsImV4cCI6MTUzMjI4MDQxNCwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwiaWF0IjoxNTMyMjc2ODE0LCJuYW1lIjoiS3lsZSBEaXhsZXIiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDUuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1CZnZfY3NOUWRHWS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BQW5uWTdyUWZfVmE5NDFpZnZYMFMxdFViYl9qU2x6OVBRL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJLeWxlIiwiZmFtaWx5X25hbWUiOiJEaXhsZXIiLCJsb2NhbGUiOiJlbiJ9.H9LJ8do7VqUepbYI4t2Mr1yrvTQJYvUnoy7XGdbXTdKUhHYG7VKpoWd76MkT77oujXQhpMJ60c_BYhCo8EpxufM75gYhVWhak4L1MOWIeHKwJFfRxu9kWVsgBtsAwsUKe4J4ZswA06bLQP10i9GB1Fjo0bimRaTfFyWTDdO_UfpMVw1QvsdPFUqhTGAmqHY3dBU3sNBMZcI3LtFCUVAlCR3-YFDsOvklyhRrwNV_oqDh6HoCL4IJGViI6LMpAdSJjNElFzitlEXUV8ET2Fw1FsAxs-PPKNmYVgI1119yYKmmm_KQJoZI5vwIZtzZKdzh6pnV7SRlWtcKASh-ynr0lg",
{
"token": "251",
"netid": "kdixle2"
}
Stores user credentials to allow for secrets, token is the tokenized user name generated from the first time the user entered salted with their netid, we don’t need to query the board to reverse the tokenization.
{
"netid": "kdixle2",
"admin": true,
"uin": "666666666",
"tokenid": "c1f8b18730737be44ea82b2a9ffd887729bd9ded1960e4b38bf49492fb0d1fa3"
}
Store all of the submissions across every assignment
// the asset id is generated using the submission time and the shasum of the submission
{
"assetId": "c1f8b18730737be44ea82b2a9ffd887729bd9ded1960e4b38bf49492fb0d1fa3",
"netid": "kdixle2",
"assignmentId": 0,
"submissionDate": "2018-07-02 11:59",
"state": "SUBMITTED"|"SCORED",
"score": 100,
"scoreBreakdown": {
"some_field": 100,
"other_field": 100,
"my_field": 100,
"field": 100,
"feld": 100
}
}
we will be using the tokenid as the HASH key and the assignmentId as the RANGE key Store the highest scores for each student tokenid should be unique to each assignmentId
{
// the asset id is generated using the submission time and the shasum of the submission
"assetId": "c1f8b18730737be44ea82b2a9ffd887729bd9ded1960e4b38bf49492fb0d1fa3",
"assignmentId": 0,
"tokenid": "c1f8b18730737be44ea82b2a9ffd887729bd9ded1960e4b38bf49492fb0d1fa3",
"submissionDate": "2018-07-02 11:59",
"state": "SUBMITTED"|"SCORED",
"score": 100,
"scoreBreakdown": {
"some_field": 100,
"other_field": 100,
"my_field": 100,
"field": 100,
"feld": 100
}
}
Store the data for each assignment using [-1] as the metadata index
{
"assignmentId": 0,
"assignmentName": "Linked Lists",
"deadline": "2018-07-02 11:59",
"score": 100,
"scoreBreakdown": {
// these are user defined and are up to the user to keep coherent
"some_field": 100,
"other_field": 100,
"my_field": 100,
"field": 100,
"feld": 100
}
}
Store resource names and mappings to make this solution more generic
{
"CRN": 44322,
"metadata": {
"instructor": "John Lillis",
"course_number": "251",
"course_name": "Data Structures"
},
"s3": {
"documents-bucket": "$bucketname"
},
"dynamodb": {
"users-table": "$tablename",
"assignments-table": "$tablename",
"submissions-table": "$tablename"
}
"sqs": {
"grading-queue": "$queuename"
}
}
Stores everyone’s submissions and the testing deploy packages. Namespace appears like this
s3://documents-bucket.name/assignment.assignmentId/…
Deploy Packages are stored at:
{$configuration-table[$CRN]["s3"]["documents-bucket"]}/{$assignments-table[$assignmentId]}/"deploymentPackage.zip"
// uic-documents-bucket-44322-fa2018/0/deploymentPackage.zip
Assets are stored at:
{$configuration-table[$CRN]["s3"]["documents-bucket"]}/{$assignments-table[$assignmentId].assignmentId}/{sha256hash(asset+$submissionTime+$random)}
// uic-documents-bucket-44322-fa2018/0/c1f8b18730737be44ea82b2a9ffd887729bd9ded1960e4b38bf49492fb0d1fa3
// the lookup in the assignments table is done as a check to see if the assignment is valid otherwise an exception will be thrown
stores the list of assets to be graded. using content based deduplication:
- Our assetIds should be unique thus our messages will be unique so we should use extra protections
{
"assetId": "{assetId}",
"assetBucket": "{documents-bucket-name}",
"assignmentId": "{assignmentId}"
}
Responsibility to get a new login token and associate it with the current user. If that user doesn’t exist, make them exist and populate their users-table entry using the hash of the current time
Read:- users-table
- tokens-table
- users-table
Responsibility to verify that the token is legitimate
Read:- tokens-table
- tokens-table
- tokens-table (validation)
- assignments-table (validation)
- users-table (retrieve netid)
- submissions-table
provisions the submission-table entry to be modified later
// the asset id is generated using the submission time and the shasum of the submission
{
"assetId": "c1f8b18730737be44ea82b2a9ffd887729bd9ded1960e4b38bf49492fb0d1fa3",
"assignmentId": 0,
"netid": "kdixle2",
"state": "SUBMITTED"
"score": 0,
"scoreBreakdown": {}
}
INDIRECTLY -> scoreboard-table Workflow may modify this table if this is the new highest score
if score > currentScore:
updateScoreboard(netid, assetId, assignmentId)
- documents-bucket/$assignmentId/{sha256sum(asset+submissionTime+random)}
- grading-queue
[PROTECTED][ADMIN] createAssignment(token, assignmentName, deploymentPackage, deadline, scoreBreakdown)
Need to make sure to validate that the user is an admin
Read:- tokens-table(validation isUser)
- users-table(validation isAdmin)
- assignments-table
{
"assignmentId": {GENERATED},
"assignmentName": $assignmentName,
"deadline": $deadline,
"score": $scoreBreakdown
}
// call params, base64 encoded
{
"token": "251",
"assignmentName": "Linked Lists",
"deadline": "2018-12-02 11:59",
"deploymentPackage": "I2luY2x1ZGUgPHN0ZGlvLmg+CiNpbmNsdWRlIDxzdGRsaWIuaD4KCmludCBtYWluKCl7CiAgIHByaW50ZigiSGVsbG8gV29ybGQhXG4iKTsKICAgcmV0dXJuIDA7Cn0K",
"scoreBreakdown": {
"score": 100,
"scoreBreakdown": {
"some_field": 100,
"other_field": 100,
"my_field": 100,
"field": 100,
"feld": 100
}
}
}
- documents-bucket/{assignments-table[assignmentId]}/deploymentPackage.zip
Writes a score in for the submission.
Read:Write:- submissions-table
When the submissions-table is modified, if the $SUBMISSION.state is ‘SCORED’, then if the $SUBMISSION.score is larger than the current scoreboard submission for getTokenId($SUBMISSION.netid) replace current scoreboard entry with tokenizedSubmission($ENTRY)
its the highest of
Read:- submissions-table
- scoreboard-table
What if it’s being extended after it’s due?
Read:- tokens-table
- users-table
- assignments-table
{
"assignmentId": assignmentId,
"deadline": $deadline
}
Not my problem if the rubric is broken This may require a change of the deployment package
Read:- tokens-table
- users-table
- assignments-table
{
"assignmentId": assignmentId,
"deadline": $deadline
}
Not my problem if the deployment package is broken. This is a future reach goal This may require a change of the scoring
Read:- tokens-table(validate isUser)
- users-table(validate isAdmin)
- documents-bucket/{assignments-table[assignmentId]}/deploymentPackage.zip
- tokens-table
- assignments-table(validate)
- submissions-table
- tokens-table
- assignments-table
- scoreboard-table
- tokens-table
- assignments-table
get user’s credentials this will be used to determine if the session token is legitimate returns {} if invalid
Read:- tokens-table
- users-table
Set quarantine on the calling asset… This endpoint/function is essentially the sudo button that we have to hide
Read:Write:- attach-role to instance
- detach-role from instance
We use environment variables since they are consistent given that we use terraform to provision
Read:- tokens-table
getTokenId by retrieving the tokenizedId of this user
Tokenizing by hashing the netid and uin fails since the netid is public information and the uin is within 100,000,000, so it takes 100,000,000 local guesses to brute force 1 user’s token. Due to this, I’ve decided to assign tokens by hashing the time that they first logged in and some salt if necessary
Read:- users-table
- tokens-table
- users-table
- submissions-table
- tokens-table
- users-table
This is going to be extremely common
def insertDynamoDB(tablename, json):
client = boto3.client('dynamodb')
table_name = os.environ[endpoint]
# TODO
returns {} if successful else None
def retrieveDynamoDB(tablename, json):
client = boto3.client('dynamodb')
table_name = os.environ[endpoint]
# TODO
Changes currently selected item Refreshes currently selected menu
frontend-assets-bucket
This is the largest security hazard that we have to deal with.
The grading resource needs access to the following(either directly or indirectly):
- S3
- DynamoDB
- SQS
No access
Monitor abnormally large files. Possibly a CloudWatch event pushing to an SNS notification.
- All code is run as a non-privileged user
- Remove internet gateway to prevent data exfiltration
- Invoke lambdas to assign and remove necessary permissions from the box as it needs them. Reduce the AWS permissions that the box has access to as the non privileged user runs his code and return them in privileged mode.
Set quarantine on the calling asset… This endpoint/function is essentially the sudo button that we have to hide
1 grading job at a time per instance, due to quarantine race conditions.
In order to perform our security role in this manner, we must provide features
/deploy/quarantine.py /deploy/cloudgrade.sh deploy/uploadScores.py /deploy/package.zip -> /deploy/package
package/ package/deploy.sh -> should generate: package/score.json
This grading workflow is run on a kubernetes cluster with docker containers running nodejs fetching jobs from an AWS SQS queue. TODO have to make it so if a machine keels over, the asset still gets graded.
In order to avoid extensive instructor permission, we will be using custom grading amis
build assets:
- gcc
- g++
- make
- gnu coreutils
grading assets:
- aws-cli
- python
- python-boto3
users:
- user #unprivileged user
const endpoints = configuration-table['HARDCODED-CRN']; // TODO Generalize
const submissionsTable = endpoints['submissions-table'];
const documentsBucket = endpoints['documents-bucket'];
const gradingQueue = endpoints['grading-queue'];
// to get job information
const job = gradingQueue.retrieveJob()
gradingQueue.sendReceipt()
/*
// job:
{
"netid": "$netid",
"assignmentId": "$assignmentId",
"assetId": "$assetId"
}
*/
const submission = submissionsTable[assetId];
const deployPackage = 'documents-bucket/' + submissions-table[assetId].assignmentId + '/deployPackage.zip'
const asset = 'documents-bucket/' + submissions-table[assetId].assignmentId + '/' + submission.assetId;
retrieveAssets(deployPackage); // downloads the testing package
retrieveAssets(asset); // downloads the asset to be evaluated
executeGradingScript('/deploy/cloudgrade.sh', job) // kicks off the grading script to generate ./package/score.json and then will call a script to upload the scores
// job is not private data
// SHELL CMDLINE arguments are visible to users
#!/bin/bash
#filename: /deploy/cloudgrade.sh
# STORE ENDPOINTS
cd /deploy/
# set ./deploy/package owner to user
# will cause this package to get reaped after upload
chgrp user ./package -R
chown user ./package -R
chmod 700 ./package -R
cd ./package
sudo -u user ./deploy.sh
cd /deploy
# "/deploy/package/score.json" contains the score that should match the scoreBreakdown object
./uploadScores.py "$1" "$(cat ./package/score.json)"
# clean up all files by user to maintain the clean state
find / -user 'user' -exec rm -fr {} \;
exit
submissionsTableEndpoint is required and can be retrieved from the configuration-table insert the score and scoreBreakdown and ‘SCORED’ into the submissions-table[assetId]
// package/score.json
{
"score": 100,
"scoreBreakdown": {
// these are user defined and are up to the user to keep coherent
"some_field": 100,
"other_field": 100,
"my_field": 100,
"field": 100,
"feld": 100
}
}
- [X] OAuth implementation