Skip to content

Commit

Permalink
Check user permissions before promoting data science projects (openda…
Browse files Browse the repository at this point in the history
…tahub-io#991)

* Use access token to promote projects

* update to use access token

* Use pass-through API and selfSubjectAccessReview to check the user permissions

* lint

* address comments
  • Loading branch information
DaoDaoNoCode authored and bartoszmajsak committed Mar 30, 2023
1 parent 59bc0a5 commit 2f3abab
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 24 deletions.
55 changes: 35 additions & 20 deletions backend/src/routes/api/k8s/pass-through.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { FastifyRequest } from 'fastify';
import https, { RequestOptions } from 'https';
import { K8sResourceCommon, K8sStatus, KubeFastifyInstance } from '../../../types';
import { DEV_MODE, USER_ACCESS_TOKEN } from '../../../utils/constants';
import {
K8sResourceCommon,
K8sStatus,
KubeFastifyInstance,
OauthFastifyRequest,
} from '../../../types';
import { DEV_MODE } from '../../../utils/constants';
import { getDirectCallOptions } from '../../../utils/directCallUtils';

type PassThroughData = {
Expand All @@ -10,13 +14,29 @@ type PassThroughData = {
url: string;
};

const isK8sStatus = (data: Record<string, unknown>): data is K8sStatus => data.kind === 'Status';
export const isK8sStatus = (data: unknown): data is K8sStatus =>
(data as K8sStatus).kind === 'Status';

const setupRequest = async (
fastify: KubeFastifyInstance,
request: FastifyRequest<{ Headers: { [USER_ACCESS_TOKEN]: string } }>,
request: OauthFastifyRequest,
data: PassThroughData,
): Promise<{ url: string; requestOptions: RequestOptions }> => {
): Promise<RequestOptions> => {
const { method, url } = data;

const requestOptions = await getDirectCallOptions(fastify, request, url);

return {
...requestOptions,
method,
};
};

export const passThrough = <T extends K8sResourceCommon>(
fastify: KubeFastifyInstance,
request: OauthFastifyRequest,
data: PassThroughData,
): Promise<T | K8sStatus> => {
const { method, url } = data;

// TODO: Remove when bug is fixed - https://issues.redhat.com/browse/HAC-1825
Expand All @@ -33,26 +53,22 @@ const setupRequest = async (
safeURL = queryParams.join('?');
}

const requestOptions = await getDirectCallOptions(fastify, request, url);

return {
const updatedData = {
...data,
url: safeURL,
requestOptions: {
...requestOptions,
method,
},
};
return safeURLPassThrough(fastify, request, updatedData);
};

export const passThrough = (
export const safeURLPassThrough = <T extends K8sResourceCommon>(
fastify: KubeFastifyInstance,
request: FastifyRequest<{ Headers: { [USER_ACCESS_TOKEN]: string } }>,
request: OauthFastifyRequest,
data: PassThroughData,
): Promise<K8sResourceCommon> => {
const { method, requestData } = data;
): Promise<T | K8sStatus> => {
const { method, requestData, url } = data;

return new Promise((resolve, reject) => {
setupRequest(fastify, request, data).then(({ url, requestOptions }) => {
setupRequest(fastify, request, data).then((requestOptions) => {
if (requestData) {
requestOptions.headers = {
...requestOptions.headers,
Expand All @@ -74,7 +90,7 @@ export const passThrough = (
data += chunk;
})
.on('end', () => {
let parsedData: K8sResourceCommon | K8sStatus;
let parsedData: T | K8sStatus;
try {
parsedData = JSON.parse(data);
} catch (e) {
Expand All @@ -83,7 +99,6 @@ export const passThrough = (
parsedData = {
kind: 'Status',
apiVersion: 'v1',
metadata: {},
status: 'Failure',
message: data,
reason: 'NotFound',
Expand Down
2 changes: 1 addition & 1 deletion backend/src/routes/api/namespaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default async (fastify: KubeFastifyInstance): Promise<void> => {

const context = parseInt(contextAsString) as NamespaceApplicationCase;

return applyNamespaceChange(fastify, name, context);
return applyNamespaceChange(fastify, request, name, context);
},
);
};
49 changes: 46 additions & 3 deletions backend/src/routes/api/namespaces/namespaceUtils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,40 @@
import { PatchUtils } from '@kubernetes/client-node';
import { PatchUtils, V1SelfSubjectAccessReview } from '@kubernetes/client-node';
import { NamespaceApplicationCase } from './const';
import { KubeFastifyInstance } from '../../../types';
import { K8sStatus, KubeFastifyInstance, OauthFastifyRequest } from '../../../types';
import { createCustomError } from '../../../utils/requestUtils';
import { isK8sStatus, safeURLPassThrough } from '../k8s/pass-through';

export const applyNamespaceChange = (
const checkNamespacePermission = (
fastify: KubeFastifyInstance,
request: OauthFastifyRequest,
name: string,
): Promise<V1SelfSubjectAccessReview | K8sStatus> => {
const kc = fastify.kube.config;
const cluster = kc.getCurrentCluster();
const selfSubjectAccessReviewObject: V1SelfSubjectAccessReview = {
apiVersion: 'authorization.k8s.io/v1',
kind: 'SelfSubjectAccessReview',
spec: {
resourceAttributes: {
group: 'project.openshift.io',
resource: 'projects',
subresource: '',
verb: 'update',
name,
namespace: name,
},
},
};
return safeURLPassThrough<V1SelfSubjectAccessReview>(fastify, request, {
url: `${cluster.server}/apis/authorization.k8s.io/v1/selfsubjectaccessreviews`,
method: 'POST',
requestData: JSON.stringify(selfSubjectAccessReviewObject),
});
};

export const applyNamespaceChange = async (
fastify: KubeFastifyInstance,
request: OauthFastifyRequest,
name: string,
context: NamespaceApplicationCase,
): Promise<{ applied: boolean }> => {
Expand All @@ -17,6 +47,19 @@ export const applyNamespaceChange = (
);
}

const selfSubjectAccessReview = await checkNamespacePermission(fastify, request, name);
if (isK8sStatus(selfSubjectAccessReview)) {
throw createCustomError(
selfSubjectAccessReview.reason,
selfSubjectAccessReview.message,
selfSubjectAccessReview.code,
);
}
if (!selfSubjectAccessReview.status.allowed) {
fastify.log.error(`Unable to access the namespace, ${selfSubjectAccessReview.status.reason}`);
throw createCustomError('Forbidden', "You don't have the access to update the namespace", 403);
}

let labels = {};
switch (context) {
case NamespaceApplicationCase.DSG_CREATION:
Expand Down

0 comments on commit 2f3abab

Please sign in to comment.