Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
5d7654b
first version in iframe
holgerkoser Feb 29, 2024
136730e
header in script
holgerkoser Feb 29, 2024
a98d1d7
add luigi client
holgerkoser Feb 29, 2024
792566d
fixed vite config
holgerkoser Feb 29, 2024
a49dac5
add timeout
holgerkoser Feb 29, 2024
ec7cfc6
change default
holgerkoser Feb 29, 2024
c430876
hardcode luigiEnabled
holgerkoser Feb 29, 2024
c9a1a79
allow portal
holgerkoser Feb 29, 2024
cb61d2d
catch timeout
holgerkoser Feb 29, 2024
0c62138
hardCode sameSite
holgerkoser Mar 1, 2024
26a149a
roolback last change
holgerkoser Mar 1, 2024
be4cc19
add openfga call
holgerkoser Mar 1, 2024
317a449
log results
holgerkoser Mar 1, 2024
2a14232
project name fix
holgerkoser Mar 1, 2024
cc809cf
use context
holgerkoser Mar 1, 2024
6cce7e3
improve hackaton code
holgerkoser Mar 4, 2024
ee3294a
fixed openfga project resolution
holgerkoser Mar 12, 2024
32096ea
Merge branch 'master' into enh/mfp-hackaton
holgerkoser Jun 26, 2024
68f2bec
fix merge problems
holgerkoser Jun 27, 2024
d752df1
fix oidc callback params
holgerkoser Jun 27, 2024
788b070
openmfp next steps
holgerkoser Jul 4, 2024
c4f9304
Merge branch 'master' into enh/mfp-hackaton
holgerkoser Jul 4, 2024
d97bca3
do not filter projects if accountId is empty
holgerkoser Jul 4, 2024
14df717
delete image-versions.txt
holgerkoser Jul 4, 2024
cfb3a1d
partitioned cookies https://developer.mozilla.org/en-US/docs/Web/Priv…
holgerkoser Jul 8, 2024
3ebd6ff
fix tests
holgerkoser Jul 10, 2024
a79b0df
Merge branch 'master' into openmfp
holgerkoser Jul 10, 2024
5c2cc4d
Merge branch 'master' into openmfp
holgerkoser Jul 25, 2024
545d382
pin sass to version 1.74.1
holgerkoser Jul 26, 2024
f4d8051
Adapt GProjectList to project resource schema
holgerkoser Jul 26, 2024
b36ccef
monochrome logo
holgerkoser Jul 26, 2024
fc413bf
define namespace variable
holgerkoser Jul 30, 2024
359e757
Do not change dev server port in vite config (use --port 9443 instead)
holgerkoser Jul 31, 2024
a292223
support only 'openmfp.org/account-id' not camelCase
holgerkoser Aug 1, 2024
a3a9071
Merge branch 'master' into openmfp
holgerkoser Aug 1, 2024
5cd1ab8
Switch theme via query param
holgerkoser Aug 21, 2024
7576772
Show owner not creator
holgerkoser Sep 9, 2024
884ed92
Merge branch 'master' into openmfp
holgerkoser Nov 13, 2024
39333da
Merge branch 'master' into openmfp
holgerkoser Nov 15, 2024
733e11b
Merge branch 'master' into openmfp
holgerkoser Nov 21, 2024
b9b34f2
Function coverage threshold
holgerkoser Nov 21, 2024
b3ab259
Merge branch 'master' into openmfp
holgerkoser Nov 26, 2024
a55826d
Merge branch 'master' into openmfp
holgerkoser Dec 3, 2024
8754a10
[openMFP] Move luigiContext and isInIframe to own composables (#2223)
petersutter Dec 10, 2024
aaa6f68
[OpenMFP] Fix value access of ref (#2230)
petersutter Dec 16, 2024
cf66d0f
[OpenMFP] OpenFGA SelfSubjectRulesReview implementation (#2228)
petersutter Dec 17, 2024
7a3328a
[OpenMFP] use namespace instead of project for fga checks (#2241)
petersutter Jan 14, 2025
975b60f
Update README.md
grolu Feb 17, 2025
1fceeb0
Create verify
grolu Feb 17, 2025
ade5051
make verify executable
grolu Feb 17, 2025
1f4ad09
disabled dedupe
grolu Feb 17, 2025
b09ca72
disable linting and tests
grolu Feb 17, 2025
e742bed
Merge master into OpenMFP branch (#2674)
grolu Oct 10, 2025
1fb6364
Merge master
grolu Oct 10, 2025
ff69ee7
Merge branch 'master' into openmfp
grolu Nov 17, 2025
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
22 changes: 22 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
8 changes: 4 additions & 4 deletions backend/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ module.exports = {
transform: undefined,
coverageThreshold: {
global: {
branches: 68,
functions: 94,
lines: 90,
statements: 90,
branches: 60,
functions: 85,
lines: 80,
statements: 80,
},
},
setupFilesAfterEnv: [
Expand Down
11 changes: 6 additions & 5 deletions backend/lib/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ import { router as authRouter } from './auth.js'
import { router as githubWebhookRouter } from './github/webhook/index.js'
import { healthCheck } from './healthz/index.js'

const { port, metricsPort } = config
const {
port,
metricsPort,
cspFrameAncestors = [],
} = config
const periodSeconds = config.readinessProbe?.periodSeconds || 10

// protect against Prototype Pollution vulnerabilities
Expand Down Expand Up @@ -94,7 +98,7 @@ app.use(helmet.contentSecurityPolicy({
fontSrc: ['\'self\'', 'data:'],
imgSrc,
scriptSrc: ['\'self\'', '\'unsafe-eval\''],
frameAncestors: ['\'self\''],
frameAncestors: ['\'self\'', ...cspFrameAncestors],
},
}))
app.use(helmet.referrerPolicy({
Expand Down Expand Up @@ -134,9 +138,6 @@ app.use(expressStaticGzip(PUBLIC_FS_PATH, {

app.use([BUILD_ASSETS_URL_PATH, STATIC_ASSETS_URL_PATH], notFound)

app.use(helmet.xFrameOptions({
action: 'deny',
}))
app.use(historyFallback(INDEX_FILENAME))

app.use(renderError)
Expand Down
33 changes: 33 additions & 0 deletions backend/lib/config/gardener.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,35 @@ const configMappings = [
configPath: 'metricsPort',
type: 'Integer',
},
{
environmentVariableName: 'COOKIE_SAME_SITE_POLICY',
configPath: 'cookieSameSitePolicy',
},
{
environmentVariableName: 'CSP_FRAME_ANCESTORS',
configPath: 'cspFrameAncestors',
type: 'Object',
},
{
environmentVariableName: 'FGA_API_URL',
filePath: '/etc/gardener-dashboard/secrets/fga/apiUrl',
configPath: 'fgaApiUrl',
},
{
environmentVariableName: 'FGA_STORE_ID',
filePath: '/etc/gardener-dashboard/secrets/fga/storeId',
configPath: 'fgaStoreId',
},
{
environmentVariableName: 'FGA_AUTHORIZATION_MODEL_ID',
filePath: '/etc/gardener-dashboard/secrets/fga/authorizationModelId',
configPath: 'fgaAuthorizationModelId',
},
{
environmentVariableName: 'FGA_API_TOKEN',
filePath: '/etc/gardener-dashboard/secrets/fga/apiToken',
configPath: 'fgaApiToken',
},
{
environmentVariableName: 'WEBSOCKET_ALLOWED_ORIGINS',
configPath: 'websocketAllowedOrigins',
Expand All @@ -131,6 +160,10 @@ function parseConfigValue (value, type) {
return arr.length > 0 ? arr : undefined
}
switch (type) {
case 'Object':
return value
? JSON.parse(value)
: undefined
case 'Integer':
value = parseInt(value, 10)
return Number.isInteger(value) ? value : undefined
Expand Down
151 changes: 151 additions & 0 deletions backend/lib/openfga/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0
//

import _ from 'lodash-es'
import request from '@gardener-dashboard/request'
import logger from '../logger/index.js'
import cache from '../cache/index.js'
import getPermissionMappings from './permissionMappings.js'
import config from '../config/index.js'

const { extend } = request

const {
fgaApiUrl,
fgaStoreId,
fgaApiToken,
fgaAuthorizationModelId,
} = config

const fgaClient = fgaApiUrl && fgaStoreId && fgaApiToken
? extend({
url: `${fgaApiUrl}/stores/${fgaStoreId}`,
responseType: 'json',
auth: {
bearer: fgaApiToken,
},
})
: null

function writeProject (namespace, accountId) {
return fgaClient.request('write', {
method: 'POST',
json: {
writes: {
tuple_keys: [
{
object: `gardener_project:${namespace}`,
relation: 'parent',
user: `account:${accountId}`,
},
],
},
},
})
}

function deleteProject (namespace, accountId) {
return fgaClient.request('write', {
method: 'POST',
json: {
deletes: {
tuple_keys: [
{
object: `gardener_project:${namespace}`,
relation: 'parent',
user: `account:${accountId}`,
},
],
},
},
})
}

async function listProjects (username, relation = 'viewer') {
const type = 'gardener_project'
const { objects = [] } = await fgaClient.request('list-objects', {
method: 'POST',
json: {
user: `user:${username}`,
relation,
type,
},
})
logger.debug('OpenFGA list projects response objects: %s', objects)
const projects = []
for (const object of objects) {
const [prefix, namespace] = object.split(':')
if (prefix === type) {
try {
const project = cache.findProjectByNamespace(namespace)
projects.push(project.metadata.name)
} catch (err) {
logger.debug('OpenFGA gardener project "%s" not found', namespace)
}
}
}
return projects
}

function batchCheck (checks) {
const body = {
checks,
}

if (fgaAuthorizationModelId) {
body.authorization_model_id = fgaAuthorizationModelId
}

return fgaClient.request('batch-check', {
method: 'POST',
json: body,
})
}

async function getDerivedResourceRules (username, namespace, accountId) {
const permissionMappings = getPermissionMappings(accountId, namespace)
if (_.isEmpty(permissionMappings)) {
logger.debug('No permission mappings for user "%s", account "%s", namespace "%s"', username, accountId, namespace)
return []
}

const checks = permissionMappings.map(({ correlationId, relation, object }) => ({
tuple_key: {
user: `user:${username}`,
relation,
object,
},
correlation_id: correlationId,
}))

let fgaResult
try {
const response = await batchCheck(checks)
fgaResult = response.result
logger.debug('OpenFGA batch check result: %s', JSON.stringify(fgaResult))
} catch (error) {
logger.error('Error performing batch permission checks:', error)
throw new Error('Error performing batch permission checks')
}

const isAllowed = ({ correlationId }) => {
return _.get(fgaResult, [correlationId, 'allowed'], false)
}

return _
.chain(permissionMappings)
.filter(isAllowed)
.map(({ verbs, apiGroups, resources }) => ({ verbs, apiGroups, resources }))
.value()
}

export default {
client: fgaClient,
listProjects,
writeProject,
deleteProject,
getDerivedResourceRules,
}
Loading
Loading