-
Notifications
You must be signed in to change notification settings - Fork 12
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
Feature/refresh #273
Feature/refresh #273
Changes from 5 commits
362cf06
096ed8f
248be52
cca5950
713fc1f
9b6c9a4
a0ee102
c77cfdc
91403ea
1498b60
71a4fe3
5e6193b
44a4e97
19c0de2
1b5a2df
f6b3136
0e8d792
09b84cd
d13f30a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,7 @@ export const getAccessibleResourcesFromArboristasync = async (jwt) => { | |
], | ||
}; | ||
} else { | ||
data = await arboristClient.listAuthorizedResources(jwt); | ||
data = await arboristClient.listAuthMapping(jwt); | ||
} | ||
|
||
log.debug('[authMiddleware] list resources: ', JSON.stringify(data, null, 4)); | ||
|
@@ -27,10 +27,39 @@ export const getAccessibleResourcesFromArboristasync = async (jwt) => { | |
} | ||
throw new CodedError(data.error.code, data.error.message); | ||
} | ||
|
||
data = resourcePathsWithServiceMethodCombination(data, ['guppy', '*'], ['read', '*']) | ||
const resources = data.resources ? _.uniq(data.resources) : []; | ||
return resources; | ||
}; | ||
|
||
export const canRefresh = async (jwt) => { | ||
let data; | ||
if (config.internalLocalTest) { | ||
data = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider fix the failed es-lint check here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
resources: [ // these are just for testing | ||
'/programs/DEV/projects/test', | ||
'/programs/jnkns/projects/jenkins', | ||
], | ||
}; | ||
} else { | ||
data = await arboristClient.listAuthMapping(jwt); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has been implemented in recent commits |
||
} | ||
|
||
log.debug('[authMiddleware] list resources: ', JSON.stringify(data, null, 4)); | ||
if (data && data.error) { | ||
// if user is not in arborist db, assume has no access to any | ||
if (data.error.code === 404) { | ||
return false; | ||
} | ||
throw new CodedError(data.error.code, data.error.message); | ||
} | ||
data = resourcePathsWithServiceMethodCombination(data, ['guppy'], ['admin_access', '*']) | ||
|
||
// Only guppy_admin resource path can control guppy admin access | ||
return data.resources ? data.resources.includes('/guppy_admin') : false; | ||
}; | ||
|
||
export const getRequestResourceListFromFilter = async ( | ||
esIndex, | ||
esType, | ||
|
@@ -49,3 +78,20 @@ export const buildFilterWithResourceList = (resourceList = []) => { | |
}; | ||
return filter; | ||
}; | ||
|
||
export const resourcePathsWithServiceMethodCombination = (userAuthMapping, services, methods = {}) => { | ||
const data = { | ||
resources: [], | ||
}; | ||
Object.keys(userAuthMapping).forEach((key) => { | ||
// logic: you have access to a project if you have | ||
// access to any of the combinations made by the method and service lists | ||
if (userAuthMapping[key] && userAuthMapping[key].some((x) => ( | ||
methods.includes(x.method) | ||
&& services.includes(x.service) | ||
))) { | ||
data.resources.push(key); | ||
} | ||
}); | ||
return data | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,22 +19,50 @@ import downloadRouter from './download'; | |
import CodedError from './utils/error'; | ||
import { statusRouter, versionRouter } from './endpoints'; | ||
|
||
let server; | ||
const app = express(); | ||
app.use(cors()); | ||
app.use(helmet()); | ||
app.use(bodyParser.json({ limit: '50mb' })); | ||
|
||
const refreshRouter = async (req, res, next) => { | ||
res.setHeader('Content-Type', 'application/json; charset=utf-8'); | ||
try { | ||
if (config.allowRefresh === false) { | ||
const disabledRefresh = new CodedError(404, '[Refresh] guppy _refresh functionality is not enabled'); | ||
throw disabledRefresh; | ||
} | ||
else if (config.allowRefresh) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this can be just an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. implemented |
||
log.debug('[Refresh] ', JSON.stringify(req.body, null, 4)); | ||
const jwt = headerParser.parseJWT(req); | ||
if (!jwt) { | ||
const noJwtError = new CodedError(401, '[Refresh] no JWT user token provided to _refresh function'); | ||
throw noJwtError; | ||
} | ||
const authHelper = await getAuthHelperInstance(jwt); | ||
if (authHelper._canRefresh === undefined || authHelper._canRefresh === false) { | ||
const noPermsUser = new CodedError(401, '[Refresh] User cannot refresh Guppy without a valid token that has admin_access method on guppy service for resource path /guppy_admin'); | ||
throw noPermsUser; | ||
} | ||
await server.stop() | ||
await initializeAndStartServer(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This solution is generally ok, but it only works if there is only 1 guppy pod/container/server in the system. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mfshao thanks for all the feedback. In the case of replica guppy pods, is there a link you could provide for how requests from the clients are distributed to the guppy replicas? In other words, is there a nginx or helm configuration that fans out the requests? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mfshao Alternatively, as a brainstorm: guppy could checking mappings on a configurable interval and initiate a restart if the version changed. For example, for index
On startup, guppy could cache this value for each index, and then monitor the index for changes |
||
} | ||
res.send("[Refresh] guppy refreshed successfully") | ||
} catch (err) { | ||
log.error(err); | ||
next(err); | ||
} | ||
return 0; | ||
}; | ||
|
||
const startServer = async () => { | ||
// build schema and resolvers by parsing elastic search fields and types, | ||
const typeDefs = getSchema(config.esConfig, esInstance); | ||
const resolvers = getResolver(config.esConfig, esInstance); | ||
const schema = makeExecutableSchema({ typeDefs, resolvers }); | ||
const schemaWithMiddleware = applyMiddleware( | ||
schema, | ||
...middlewares, | ||
); | ||
// create graphql server instance | ||
const server = new ApolloServer({ | ||
const schemaWithMiddleware = applyMiddleware(schema, ...middlewares); | ||
// create graphql server instance | ||
server = new ApolloServer({ | ||
mocks: false, | ||
schema: schemaWithMiddleware, | ||
validationRules: [depthLimit(10)], | ||
|
@@ -57,43 +85,49 @@ const startServer = async () => { | |
path: config.path, | ||
}), | ||
); | ||
log.info(`[Server] guppy listening on port ${config.port}!`); | ||
}; | ||
|
||
// simple health check endpoint | ||
// eslint-disable-next-line no-unused-vars | ||
app.get('/_status', statusRouter, (req, res, err, next) => { | ||
if (err instanceof CodedError) { | ||
// deepcode ignore ServerLeak: no important information exists in error | ||
res.status(err.code).send(err.msg); | ||
} else { | ||
// deepcode ignore ServerLeak: no important information exists in error | ||
res.status(500).send(err); | ||
} | ||
}); | ||
const initializeAndStartServer = async () => { | ||
await esInstance.initialize(); | ||
await startServer(); | ||
}; | ||
// simple health check endpoint | ||
// eslint-disable-next-line no-unused-vars | ||
app.get('/_status', statusRouter, (req, res, err, next) => { | ||
if (err instanceof CodedError) { | ||
// deepcode ignore ServerLeak: no important information exists in error | ||
res.status(err.code).send(err.msg); | ||
} else { | ||
// deepcode ignore ServerLeak: no important information exists in error | ||
res.status(500).send(err); | ||
} | ||
}); | ||
|
||
// eslint-disable-next-line no-unused-vars | ||
app.get('/_version', versionRouter); | ||
// eslint-disable-next-line no-unused-vars | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you don't need this for the next line There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
app.get('/_version', versionRouter); | ||
|
||
// download endpoint for fetching data directly from es | ||
app.post( | ||
'/download', | ||
downloadRouter, | ||
(err, req, res, next) => { // eslint-disable-line no-unused-vars | ||
if (err instanceof CodedError) { | ||
// deepcode ignore ServerLeak: no important information exists in error | ||
res.status(err.code).send(err.msg); | ||
} else { | ||
// deepcode ignore ServerLeak: no important information exists in error | ||
res.status(500).send(err); | ||
} | ||
}, | ||
); | ||
// download endpoint for fetching data directly from es | ||
app.post('/download', downloadRouter, (err, req, res, next) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. put There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
// eslint-disable-line no-unused-vars | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you don't need this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
if (err instanceof CodedError) { | ||
// deepcode ignore ServerLeak: no important information exists in error | ||
res.status(err.code).send(err.msg); | ||
} else { | ||
// deepcode ignore ServerLeak: no important information exists in error | ||
res.status(500).send(err); | ||
} | ||
}); | ||
|
||
app.listen(config.port, () => { | ||
log.info(`[Server] guppy listening on port ${config.port}!`); | ||
}); | ||
}; | ||
app.post('/_refresh',refreshRouter, (err, req, res, next) => { | ||
if (err instanceof CodedError) { | ||
res.status(err.code).send(err.msg); | ||
}else{ | ||
res.status(500).send(err) | ||
} | ||
}); | ||
|
||
// need to connect to ES and initialize before setting up a server | ||
esInstance.initialize().then(() => { | ||
startServer(); | ||
app.listen(config.port, async () => { | ||
await initializeAndStartServer(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
canRefresh => checkIfUserCanRefreshServer has been implemented