|
2 | 2 | import { constants, Http2ServerRequest, Http2ServerResponse, OutgoingHttpHeaders } from 'http2'
|
3 | 3 | import { jsonPut, jsonRequest } from '../lib/json-request'
|
4 | 4 | import { logger } from '../lib/logger'
|
5 |
| -import { respondInternalServerError } from '../lib/respond' |
| 5 | +import { respond, respondInternalServerError } from '../lib/respond' |
6 | 6 | import { getServiceAccountToken } from '../lib/serviceAccountToken'
|
7 | 7 | import { getAuthenticatedToken } from '../lib/token'
|
8 | 8 | import { ResourceList } from '../resources/resource-list'
|
9 | 9 | import { Route } from '../resources/route'
|
10 | 10 | import { Secret } from '../resources/secret'
|
| 11 | +import { canAccess } from './events' |
11 | 12 |
|
12 | 13 | const { HTTP_STATUS_INTERNAL_SERVER_ERROR } = constants
|
13 | 14 | const proxyHeaders = [
|
@@ -37,66 +38,82 @@ export async function virtualMachineProxy(req: Http2ServerRequest, res: Http2Ser
|
37 | 38 | req.on('end', async () => {
|
38 | 39 | const body = JSON.parse(chucks.join()) as ActionBody
|
39 | 40 |
|
40 |
| - // console-mce ClusterRole does not allow for GET on secrets. Have to list in a namespace |
41 |
| - const secretPath = process.env.CLUSTER_API_URL + `/api/v1/namespaces/${body.managedCluster}/secrets` |
42 |
| - const managedClusterToken: string = await jsonRequest(secretPath, serviceAccountToken) |
43 |
| - .then((response: ResourceList<Secret>) => { |
44 |
| - const secret = response.items.find((secret) => secret.metadata.name === 'vm-actor') |
45 |
| - const proxyToken = secret.data?.token ?? '' |
46 |
| - return Buffer.from(proxyToken, 'base64').toString('ascii') |
47 |
| - }) |
48 |
| - .catch((err: Error): undefined => { |
49 |
| - logger.error({ msg: `Error getting secret in namespace ${body.managedCluster}`, error: err.message }) |
50 |
| - return undefined |
51 |
| - }) |
52 |
| - |
53 |
| - // Get cluster proxy host |
54 |
| - const proxyServer = await jsonRequest( |
55 |
| - process.env.CLUSTER_API_URL + |
56 |
| - '/apis/route.openshift.io/v1/namespaces/multicluster-engine/routes/cluster-proxy-addon-user', |
| 41 | + // If user is not able to create an MCA in the managed cluster namespace -> they aren't authorized to trigger actions. |
| 42 | + const hasAuth = await canAccess( |
| 43 | + { |
| 44 | + kind: 'ManagedClusterAction', |
| 45 | + apiVersion: 'action.open-cluster-management.io/v1beta1', |
| 46 | + metadata: { namespace: body.managedCluster }, |
| 47 | + }, |
| 48 | + 'create', |
57 | 49 | token
|
58 |
| - ) |
59 |
| - .then((response: Route) => { |
60 |
| - const scheme = response?.spec?.tls?.termination ? 'https' : 'http' |
61 |
| - return response?.spec?.host ? `${scheme}://${response.spec.host}` : '' |
62 |
| - }) |
63 |
| - .catch((err: Error): undefined => { |
64 |
| - logger.error({ msg: 'Error getting cluster proxy Route', error: err.message }) |
65 |
| - return undefined |
66 |
| - }) |
| 50 | + ).then((allowed) => allowed) |
67 | 51 |
|
68 |
| - // req.url is one of: /virtualmachines/<action> OR /virtualmachineinstances/<action> |
69 |
| - // the VM name is need between the kind and action for the correct api url. |
70 |
| - const splitURL = req.url.split('/') |
71 |
| - const joinedURL = `${splitURL[1]}/${body.vmName}/${splitURL[2]}` |
72 |
| - const path = `${proxyServer}/${body.managedCluster}/apis/subresources.kubevirt.io/v1/namespaces/${body.vmNamespace}/${joinedURL}` |
73 |
| - const headers: OutgoingHttpHeaders = { authorization: `Bearer ${managedClusterToken}` } |
74 |
| - for (const header of proxyHeaders) { |
75 |
| - if (req.headers[header]) headers[header] = req.headers[header] |
76 |
| - } |
| 52 | + if (hasAuth) { |
| 53 | + // console-mce ClusterRole does not allow for GET on secrets. Have to list in a namespace |
| 54 | + const secretPath = process.env.CLUSTER_API_URL + `/api/v1/namespaces/${body.managedCluster}/secrets` |
| 55 | + const managedClusterToken: string = await jsonRequest(secretPath, serviceAccountToken) |
| 56 | + .then((response: ResourceList<Secret>) => { |
| 57 | + const secret = response.items.find((secret) => secret.metadata.name === 'vm-actor') |
| 58 | + const proxyToken = secret.data?.token ?? '' |
| 59 | + return Buffer.from(proxyToken, 'base64').toString('ascii') |
| 60 | + }) |
| 61 | + .catch((err: Error): undefined => { |
| 62 | + logger.error({ msg: `Error getting secret in namespace ${body.managedCluster}`, error: err.message }) |
| 63 | + return undefined |
| 64 | + }) |
| 65 | + |
| 66 | + // Get cluster proxy host |
| 67 | + const proxyServer = await jsonRequest( |
| 68 | + process.env.CLUSTER_API_URL + |
| 69 | + '/apis/route.openshift.io/v1/namespaces/multicluster-engine/routes/cluster-proxy-addon-user', |
| 70 | + token |
| 71 | + ) |
| 72 | + .then((response: Route) => { |
| 73 | + const scheme = response?.spec?.tls?.termination ? 'https' : 'http' |
| 74 | + return response?.spec?.host ? `${scheme}://${response.spec.host}` : '' |
| 75 | + }) |
| 76 | + .catch((err: Error): undefined => { |
| 77 | + logger.error({ msg: 'Error getting cluster proxy Route', error: err.message }) |
| 78 | + return undefined |
| 79 | + }) |
77 | 80 |
|
78 |
| - if (!path) return respondInternalServerError(req, res) |
79 |
| - await jsonPut(path, {}, managedClusterToken) |
80 |
| - .then((results) => { |
81 |
| - if (results?.statusCode >= 200 && results?.statusCode < 300) { |
82 |
| - res.setHeader('Content-Type', 'application/json') |
83 |
| - res.end(JSON.stringify(results)) |
84 |
| - } else { |
85 |
| - logger.error({ |
86 |
| - msg: 'Error in VirtualMachine action response', |
87 |
| - error: results.body.message, |
88 |
| - }) |
89 |
| - res.setHeader('Content-Type', 'application/json') |
90 |
| - res.writeHead(results.statusCode ?? HTTP_STATUS_INTERNAL_SERVER_ERROR) |
91 |
| - delete results.body?.code // code is added via writeHead |
92 |
| - res.end(JSON.stringify(results.body)) |
93 |
| - } |
94 |
| - }) |
95 |
| - .catch((err: Error) => { |
96 |
| - logger.error({ msg: 'Error on VirtualMachine action request', error: err.message }) |
97 |
| - respondInternalServerError(req, res) |
98 |
| - return undefined |
99 |
| - }) |
| 81 | + // req.url is one of: /virtualmachines/<action> OR /virtualmachineinstances/<action> |
| 82 | + // the VM name is needed between the kind and action for the correct api url. |
| 83 | + const splitURL = req.url.split('/') |
| 84 | + const joinedURL = `${splitURL[1]}/${body.vmName}/${splitURL[2]}` |
| 85 | + const path = `${proxyServer}/${body.managedCluster}/apis/subresources.kubevirt.io/v1/namespaces/${body.vmNamespace}/${joinedURL}` |
| 86 | + const headers: OutgoingHttpHeaders = { authorization: `Bearer ${managedClusterToken}` } |
| 87 | + for (const header of proxyHeaders) { |
| 88 | + if (req.headers[header]) headers[header] = req.headers[header] |
| 89 | + } |
| 90 | + |
| 91 | + if (!path) return respondInternalServerError(req, res) |
| 92 | + await jsonPut(path, {}, managedClusterToken) |
| 93 | + .then((results) => { |
| 94 | + if (results?.statusCode >= 200 && results?.statusCode < 300) { |
| 95 | + res.setHeader('Content-Type', 'application/json') |
| 96 | + res.end(JSON.stringify(results)) |
| 97 | + } else { |
| 98 | + logger.error({ |
| 99 | + msg: 'Error in VirtualMachine action response', |
| 100 | + error: results.body.message, |
| 101 | + }) |
| 102 | + res.setHeader('Content-Type', 'application/json') |
| 103 | + res.writeHead(results.statusCode ?? HTTP_STATUS_INTERNAL_SERVER_ERROR) |
| 104 | + delete results.body?.code // code is added via writeHead |
| 105 | + res.end(JSON.stringify(results.body)) |
| 106 | + } |
| 107 | + }) |
| 108 | + .catch((err: Error) => { |
| 109 | + logger.error({ msg: 'Error on VirtualMachine action request', error: err.message }) |
| 110 | + respondInternalServerError(req, res) |
| 111 | + return undefined |
| 112 | + }) |
| 113 | + } else { |
| 114 | + logger.error({ msg: `Unauthorized request ${req.url.split('/')[2]} on VirtualMachine ${body.vmName}` }) |
| 115 | + return respond(res, `Unauthorized request ${req.url.split('/')[2]} on VirtualMachine ${body.vmName}`, 401) |
| 116 | + } |
100 | 117 | })
|
101 | 118 | } catch (err) {
|
102 | 119 | logger.error(err)
|
|
0 commit comments