Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pentest #1566

Merged
merged 89 commits into from
Dec 13, 2024
Merged

Pentest #1566

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
4ec9da8
add logging
anyoussefinia Nov 27, 2024
a29a5ef
try adding cognito trigger lambda
anyoussefinia Nov 29, 2024
1fc58a5
export.main
anyoussefinia Nov 29, 2024
e479739
modify lambda response for version 2
anyoussefinia Nov 29, 2024
ec0318f
rename main to handler
anyoussefinia Nov 29, 2024
bcae194
go back to version one
anyoussefinia Nov 29, 2024
1ba363c
added logger for decoded ID token
anyoussefinia Nov 29, 2024
dae1ad5
log user role in lambda trigger
anyoussefinia Dec 2, 2024
21b496c
remove lint error
anyoussefinia Dec 2, 2024
906b2d3
try using async await
anyoussefinia Dec 2, 2024
80db87f
switch branch
anyoussefinia Dec 2, 2024
e98d55b
test if batch job is possible
anyoussefinia Dec 2, 2024
124562a
fix lint errors
anyoussefinia Dec 2, 2024
71b385a
fix lint errors
anyoussefinia Dec 3, 2024
2d1d3ec
fix lint errors
anyoussefinia Dec 3, 2024
46dd3a5
access Users nested property
anyoussefinia Dec 3, 2024
ee2b4d7
added await logic to trigger funciton
anyoussefinia Dec 3, 2024
f13baad
try adding roles in response ID token
anyoussefinia Dec 3, 2024
bdb4aa4
added logging
anyoussefinia Dec 3, 2024
49bdbc5
modify isAdminUser logic
anyoussefinia Dec 3, 2024
43a31f2
remove function
anyoussefinia Dec 3, 2024
296c703
ignore lint
anyoussefinia Dec 3, 2024
dfb8b58
ignore lint
anyoussefinia Dec 3, 2024
1a063d6
fix for in to for of
anyoussefinia Dec 3, 2024
b3e2b3c
modify UserPage to use idToken email instead of app context userProf…
anyoussefinia Dec 3, 2024
1ee9531
add in needed import
anyoussefinia Dec 3, 2024
9c2cf4b
modify isAdminUser check
anyoussefinia Dec 3, 2024
482b372
remove .some from admin user check logic
anyoussefinia Dec 4, 2024
241359a
modify isAdminUser to parse the string array coming from idToken
anyoussefinia Dec 4, 2024
d44ff81
added logging
anyoussefinia Dec 4, 2024
6053e1f
added logging
anyoussefinia Dec 4, 2024
3c6114b
fix for loop statement
anyoussefinia Dec 4, 2024
11a3f36
fix for loop statement
anyoussefinia Dec 4, 2024
a3910d0
remove logging
anyoussefinia Dec 5, 2024
27602b5
add protection for query param
anyoussefinia Dec 6, 2024
cddf88a
import Redirect react router
anyoussefinia Dec 6, 2024
ba77a1a
remove duplicate import
anyoussefinia Dec 6, 2024
196e274
move funciton inside component
anyoussefinia Dec 6, 2024
38096e8
eslint fix
anyoussefinia Dec 6, 2024
402cef2
fix lint error
anyoussefinia Dec 6, 2024
429365a
ignore lint
anyoussefinia Dec 6, 2024
00e0c26
fix
anyoussefinia Dec 6, 2024
e6fda5e
lint
anyoussefinia Dec 6, 2024
e23b30b
lint
anyoussefinia Dec 6, 2024
05e73da
redirect user not found
anyoussefinia Dec 6, 2024
4749749
ignore lint
anyoussefinia Dec 6, 2024
c97d0d5
added trigger function to serverless
anyoussefinia Dec 6, 2024
f3b26a8
remove !GettAtt from serverless.yml
anyoussefinia Dec 6, 2024
7f05351
try adding lambda arn
anyoussefinia Dec 6, 2024
38572ef
try ref instead of arn
anyoussefinia Dec 6, 2024
680f6a6
try Fn::GetAtt
anyoussefinia Dec 6, 2024
d80e3a6
try putting resources after functions
anyoussefinia Dec 6, 2024
c57e21c
try admin-${self:custom.stage}-addPropertyToJWT
anyoussefinia Dec 6, 2024
046a1ee
try arn refference again
anyoussefinia Dec 6, 2024
946c06f
remove resource
anyoussefinia Dec 6, 2024
eee0331
try removing resource
anyoussefinia Dec 6, 2024
c2a1904
try using user pool id
anyoussefinia Dec 6, 2024
3a6e6e8
test
anyoussefinia Dec 6, 2024
2f3d84e
revert
anyoussefinia Dec 6, 2024
4605e2a
try with existing: true to prevent duplicate pools
anyoussefinia Dec 9, 2024
5246834
try adding iam create roll permissions
anyoussefinia Dec 9, 2024
615a44a
try iam:passRole
anyoussefinia Dec 9, 2024
cf6a916
revert
anyoussefinia Dec 9, 2024
d23bb62
revert
anyoussefinia Dec 9, 2024
b1990bd
add back trigger function
anyoussefinia Dec 9, 2024
bff86f0
remove existing: true
anyoussefinia Dec 10, 2024
af24ad7
try to allow iam:create role in permission boundary
anyoussefinia Dec 10, 2024
9b93afc
add back existing true
anyoussefinia Dec 10, 2024
c827c39
remove existing true to get working
anyoussefinia Dec 10, 2024
78e514f
bring in latest develop and fix conflicts
anyoussefinia Dec 11, 2024
15c27a2
remove allow create role
anyoussefinia Dec 11, 2024
1888c43
remove add pre token auth trigger from serverless
anyoussefinia Dec 11, 2024
4bb4252
change to custom:user_roles
anyoussefinia Dec 12, 2024
e7f3619
try batch job
anyoussefinia Dec 12, 2024
e7eb97b
remove unused var
anyoussefinia Dec 12, 2024
fd8c5a5
test
anyoussefinia Dec 12, 2024
169ec77
test
anyoussefinia Dec 12, 2024
b97f818
test
anyoussefinia Dec 12, 2024
2574960
added batch function
anyoussefinia Dec 12, 2024
fd4b0dd
lint
anyoussefinia Dec 12, 2024
943f46f
duplicate user pools
anyoussefinia Dec 12, 2024
ee808f0
hard code user pool Id
anyoussefinia Dec 12, 2024
9c8316b
hard code user pool Id
anyoussefinia Dec 12, 2024
9c09938
add back keyword custom
anyoussefinia Dec 12, 2024
4d16d78
add additional logging for how many users had roles in batch job
anyoussefinia Dec 12, 2024
8a3debe
remove unneeded logging, pr cleanup
anyoussefinia Dec 13, 2024
9580202
add better logging in batch funciton
anyoussefinia Dec 13, 2024
2933f8e
PR cleanup
anyoussefinia Dec 13, 2024
81bc3d8
Merge branch 'develop' into pentest
bflynn-cms Dec 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions services/admin/handlers/addRolesToJWT.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getUser } from "../../app-api/getUser";

const handler = async (event) => {
console.log("JWT claims before modification:", JSON.stringify(event));
try{
const userEmail = event.request.userAttributes.email;
const user = await getUser(userEmail);
const roles = [];
for (const role of user.roleList) {
roles.push(role.role)
}
event.response = event.response || {};
event.response.claimsOverrideDetails = event.response.claimsOverrideDetails || {};
event.response.claimsOverrideDetails.claimsToAddOrOverride = event.response.claimsOverrideDetails.claimsToAddOrOverride || {};

// Example of adding roles dynamically from DynamoDB to the JWT claims
event.response.claimsOverrideDetails.claimsToAddOrOverride['custom:user_roles'] = JSON.stringify(roles); // Add user roles
} catch(e) {
console.log("error updating id token claims", e)
}
console.log("JWT claims after modification:", JSON.stringify(event));
return event;
};

export { handler };
76 changes: 76 additions & 0 deletions services/admin/handlers/batchUpdateCognitoUsers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import AWS from "aws-sdk";
const cognito = new AWS.CognitoIdentityServiceProvider();
import { getUser } from "../../app-api/getUser";

async function updateUserAttribute(userPoolId, username, roles) {
const params = {
UserPoolId: userPoolId,
Username: username,
UserAttributes: [
{
Name: 'custom:user_roles',
Value: JSON.stringify(roles)
}
]
};

await cognito.adminUpdateUserAttributes(params).promise();
}

async function processCognitoUsers() {
const userPoolId = process.env.USER_POOL_ID;
console.log("user pool id: ", userPoolId)
let paginationToken = null;
let counter = 0;
let hasRolesCounter = 0;
let noRolesCounter =0;
do {
const params = {
UserPoolId: userPoolId,
AttributesToGet: ['email'],
PaginationToken: paginationToken
};

const listUsersResponse = await cognito.listUsers(params).promise();
console.log(listUsersResponse.Users.length + " users found")

for (const user of listUsersResponse.Users) {
const emailAttribute = user.Attributes.find(attr => attr.Name === 'email');
if (emailAttribute) {
const userEmail = emailAttribute.Value;

try {
const externalUser = await getUser(userEmail);
let roles = [""];
let roleList;
try{
roleList = externalUser.roleList;
}catch(error) {
noRolesCounter ++
console.log(userEmail + " has no roles");
}
if (roleList && roleList.length > 0 && roleList[0] != null) {
roles = externalUser.roleList.map(role => role.role);
hasRolesCounter ++;
} else {
console.log("user parsing error for user" + userEmail)
}
await updateUserAttribute(userPoolId, user.Username, roles);
} catch (error) {
console.error(`Error processing user ${userEmail}:`, error);
}
}
counter++;
}

paginationToken = listUsersResponse.PaginationToken;
} while (paginationToken);
console.log(counter+ "users modified, "+ hasRolesCounter + "users had roles and "+ noRolesCounter + " users had no roles")
}


export const main = async () => {
await processCognitoUsers().catch(console.error);
console.log("function complete")
}

23 changes: 22 additions & 1 deletion services/admin/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@ frameworkVersion: "3"

useDotenv: true

variablesResolutionMode: 20210326

package:
individually: true

plugins:
- serverless-esbuild
- serverless-dotenv-plugin
- serverless-s3-bucket-helper

custom:
stage: ${opt:stage, self:provider.stage}
iamPermissionsBoundaryPolicy: ${ssm:/configuration/${self:custom.stage}/iam/permissionsBoundaryPolicy, ssm:/configuration/default/iam/permissionsBoundaryPolicy, ""}
oneMacTableName: onemac-${self:custom.stage}-one
userPoolName: ${self:custom.stage}-user-pool
userPoolId: ${cf:ui-auth-${self:custom.stage}.UserPoolId}

provider:
name: aws
runtime: nodejs20.x
Expand All @@ -37,14 +43,29 @@ provider:
- arn:aws:dynamodb:*:*:table/onemac-develop-one
- arn:aws:dynamodb:*:*:table/${self:custom.oneMacTableName}
- arn:aws:dynamodb:*:*:table/${self:custom.oneMacTableName}/index/*
- Effect: Allow
Action:
- cognito-idp:ListUsers
- cognito-idp:AdminUpdateUserAttributes
Resource: arn:aws:cognito-idp:${self:provider.region}:*:userpool/${self:custom.userPoolId}

environment:
NODE_OPTIONS: '--enable-source-maps'
oneMacTableName: ${self:custom.oneMacTableName}
USER_POOL_ID: ${self:custom.userPoolId}
layers:
- ${cf:aws-sdk-v2-layer-${self:custom.stage}.AwsSdkV2LambdaLayerQualifiedArn}


functions:

batchUpdateCognitoUsers:
handler: ./handlers/batchUpdateCognitoUsers.main
timeout: 360

addRolesToJWT:
handler: ./handlers/addRolesToJWT.handler
timeout: 360

resetData:
handler: ./handlers/resetData.main
timeout: 360
Expand Down
78 changes: 70 additions & 8 deletions services/ui-src/src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import React, { FC } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import jwt_decode from "jwt-decode";
import { useFlags } from "launchdarkly-react-client-sdk";

import {
Expand Down Expand Up @@ -70,6 +71,7 @@ import WaiverRenewalSubsequentSubmissionForm from "./page/waiver-renewal/WaiverR
import WaiverAmendmentSubsequentSubmissionForm from "./page/waiver-amendment/WaiverAmendmentSubsequentSubmissionForm";
import WaiverAppKSubsequentSubmissionForm from "./page/waiver-appendix-k/WaiverAppKSubsequentSubmissionForm";
import DisableRaiWithdrawForm from "./page/disable-rai-withdraw/DisableRaiWithdrawForm";
const ID_TOKEN_KEY: string = "idToken";

type RouteSpec = {
path: string;
Expand Down Expand Up @@ -141,17 +143,73 @@ const SignupGuardRouteListRenderer: FC<{ routes: RouteSpec[] }> = ({
return <RouteListRenderer routes={routes} />;
};

const isAdminUser = ()=> {
/* eslint-disable-next-line react-hooks/rules-of-hooks */
const context = useAppContext();
if(!context?.isAuthenticated) {
return false;
}

let userRoles;
//authenticated users will have idToken in Local Storage
try{
const idTokenKey: string[] = Object.keys(localStorage).filter((k) =>
k.includes(ID_TOKEN_KEY)
);
const idToken: string | null =
idTokenKey && localStorage.getItem(idTokenKey[0]);
if (!idToken) return false;
const decodedIdToken: any = jwt_decode(idToken);
userRoles = decodedIdToken["custom:user_roles"];
} catch (error) {
console.error("error decoding idToken", error);
return false;
}

const allowedRoles = [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for turning this into a whitelist of allowed roles!

"cmsroleapprover",
"systemadmin",
"statesystemadmin",
"helpdesk",
"cmsreviewer",
// "defaultcmsuser"
];

// only passes admin check if roles from jwt one of the "allowed roles"
if (userRoles) {
try {
userRoles = JSON.parse(userRoles);
} catch (error) {
console.error('Error parsing user_roles:', error);
userRoles = [];
}
for (let i = 0; i < userRoles.length; i++) {
if (allowedRoles.includes(userRoles[i])) {
return true;
}
}
}

return false;
}

const accessGuardRouteListRenderer: (
accessKey: keyof UserRole,
redirectAccessKey?: keyof UserRole,
redirectTo?: string
redirectTo?: string,
isAdminRoute?: boolean
) => FC<{ routes: RouteSpec[] }> =
(accessKey, redirectAccessKey, redirectTo) =>
(accessKey, redirectAccessKey, redirectTo, isAdminRoute) =>
({ routes }) => {
const { userProfile: { userData: { roleList = [] } = {} } = {} } =
useAppContext() ?? {};
const roleObj = getUserRoleObj(roleList);

// Token based admin check will redirect if non admin user
if(isAdminRoute && redirectTo) {
if(!isAdminUser()){
return <Redirect to={redirectTo} />;
}
}
if (roleObj[accessKey]) return <RouteListRenderer routes={routes} />;
if (redirectAccessKey && redirectTo && roleObj[redirectAccessKey])
return <Redirect to={redirectTo} />;
Expand All @@ -162,6 +220,7 @@ const ROUTE_LIST: RouteSpec[] = [
{ path: ROUTES.HOME, exact: true, component: Home },
{ path: ROUTES.FAQ, exact: true, component: FAQ },
{ path: ROUTES.DEVLOGIN, exact: true, component: DevLogin },

{
path: ROUTES.PROFILE,
component: AuthenticatedRouteListRenderer,
Expand All @@ -175,7 +234,7 @@ const ROUTE_LIST: RouteSpec[] = [
path: ROUTES.PROFILE + "/:userId",
exact: true,
component: UserPage,
},
}
],
},
{
Expand All @@ -198,16 +257,18 @@ const ROUTE_LIST: RouteSpec[] = [
accessKey: "canAccessUserManagement",
redirectAccessKey: "canAccessDashboard",
redirectTo: ONEMAC_ROUTES.PACKAGE_LIST,
isAdminRoute: true,
component: UserManagement,
},
{
path: ONEMAC_ROUTES.PACKAGE_LIST,
accessKey: "canAccessDashboard",
redirectAccessKey: "canAccessUserManagement",
redirectTo: ROUTES.USER_MANAGEMENT,
isAdminRoute: false,
component: PackageList,
},
].map(({ path, accessKey, redirectAccessKey, redirectTo, component }) => ({
].map(({ path, accessKey, redirectAccessKey, redirectTo, component, isAdminRoute }) => ({
path,
component: SignupGuardRouteListRenderer,
routes: [
Expand All @@ -216,11 +277,12 @@ const ROUTE_LIST: RouteSpec[] = [
component: accessGuardRouteListRenderer(
accessKey as keyof UserRole,
redirectAccessKey as keyof UserRole,
redirectTo
redirectTo,
isAdminRoute
),
routes: [{ path, exact: true, component }],
routes: [{ path, exact: true, component}],
},
],
]
})),
// legacy triage screens, plus current OneMACForm forms
...[
Expand Down
2 changes: 2 additions & 0 deletions services/ui-src/src/components/IdleTimerWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,13 @@ const IdleTimerWrapper = () => {
const tokenKey: string[] = Object.keys(localStorage).filter((k) =>
k.includes(STORAGE_KEY)
);

const loginToken: string | null =
tokenKey && localStorage.getItem(tokenKey[0]);
if (!loginToken) return;

const decodedToken: any = jwt_decode(loginToken);

const epochAuthTime: number | undefined = decodedToken?.auth_time;
if (!epochAuthTime) return;

Expand Down
Loading