From 35113ed81c8305ead2dc67ff8dc7694049dee06d Mon Sep 17 00:00:00 2001 From: garrettmills Date: Wed, 26 Oct 2022 02:59:43 -0500 Subject: [PATCH] Remove Vault support; fix OpenID Connect client delete issue --- .../app/resource/iam/Policy.resource.js | 29 ---- .../app/resource/vault/Vault.resource.js | 62 --------- app/controllers/OpenID.controller.js | 2 +- app/controllers/api/v1/IAM.controller.js | 26 +--- app/controllers/api/v1/Vault.controller.js | 130 ------------------ app/models/iam/Policy.model.js | 6 +- app/models/vault/Entry.model.js | 29 ---- app/models/vault/Vault.model.js | 66 --------- app/routing/routers/api/v1/vault.routes.js | 41 ------ app/unit/OpenIDConnectUnit.js | 14 ++ config/auth.config.js | 16 --- config/oidc.config.js | 3 +- config/traps.config.js | 2 - locale/en_US/api.locale.js | 1 - 14 files changed, 24 insertions(+), 403 deletions(-) delete mode 100644 app/assets/app/resource/vault/Vault.resource.js delete mode 100644 app/controllers/api/v1/Vault.controller.js delete mode 100644 app/models/vault/Entry.model.js delete mode 100644 app/models/vault/Vault.model.js delete mode 100644 app/routing/routers/api/v1/vault.routes.js diff --git a/app/assets/app/resource/iam/Policy.resource.js b/app/assets/app/resource/iam/Policy.resource.js index 9179dce..0750eb2 100644 --- a/app/assets/app/resource/iam/Policy.resource.js +++ b/app/assets/app/resource/iam/Policy.resource.js @@ -129,7 +129,6 @@ class PolicyResource extends CRUDBase { {display: 'API Scope', value: 'api_scope'}, {display: 'Computer', value: 'machine'}, {display: 'Computer Group', value: 'machine_group'}, - {display: 'Vault', value: 'vault'}, ], }, { @@ -180,18 +179,6 @@ class PolicyResource extends CRUDBase { }, if: (form_data) => form_data.target_type === 'machine_group' }, - { - name: 'Target', - field: 'target_id', - required: true, - type: 'select.dynamic', - options: { - resource: 'vault/Vault', - display: 'name', - value: 'id', - }, - if: (form_data) => form_data.target_type === 'vault' - }, { name: 'Permission', field: 'permission', @@ -256,22 +243,6 @@ class PolicyResource extends CRUDBase { }, if: (form_data, opts) => form_data.target_type === 'machine_group' && opts?.length }, - { - name: 'Permission', - field: 'permission', - required: false, - type: 'select.dynamic', - options: { - resource: 'iam/Permission', - display: 'permission', - value: 'permission', - other_params: { - target_type: 'vault', - include_unset: true, - }, - }, - if: (form_data, opts) => form_data.target_type === 'vault' && opts?.length - }, ], /*handlers: { insert: { diff --git a/app/assets/app/resource/vault/Vault.resource.js b/app/assets/app/resource/vault/Vault.resource.js deleted file mode 100644 index a3c9982..0000000 --- a/app/assets/app/resource/vault/Vault.resource.js +++ /dev/null @@ -1,62 +0,0 @@ -import CRUDBase from '../CRUDBase.js' - -class VaultResource extends CRUDBase { - constructor() { - super() - - this.endpoint = '/api/v1/vault/vaults' - this.required_fields = ['name'] - this.permission_base = 'v1:vault:vaults' - - this.item = 'Vault' - this.plural = 'Vaults' - - this.listing_definition = { - display: `Vaults are encrypted key-value stores that can be managed with IAM and accessed via REST APIs.`, - columns: [ - { - name: 'Name', - field: 'name', - }, - ], - actions: [ - { - type: 'resource', - position: 'main', - action: 'insert', - text: 'Create New', - color: 'success', - }, - { - type: 'resource', - position: 'row', - action: 'update', - icon: 'fa fa-edit', - color: 'primary', - }, - { - type: 'resource', - position: 'row', - action: 'delete', - icon: 'fa fa-times', - color: 'danger', - confirm: true, - }, - ], - } - - this.form_definition = { - fields: [ - { - name: 'Name', - field: 'name', - required: true, - type: 'text', - }, - ], - } - } -} - -const vault_vault = new VaultResource() -export { vault_vault } diff --git a/app/controllers/OpenID.controller.js b/app/controllers/OpenID.controller.js index 66817b0..88ae551 100644 --- a/app/controllers/OpenID.controller.js +++ b/app/controllers/OpenID.controller.js @@ -105,7 +105,7 @@ class OpenIDController extends Controller { const Client = this.models.get('openid:Client') const client = await Client.findById(req.params.id) - if ( !client || !client.active ) + if ( !client ) return res.status(404) .message(req.T('api.client_not_found')) .api() diff --git a/app/controllers/api/v1/IAM.controller.js b/app/controllers/api/v1/IAM.controller.js index 1b79a69..91ffbf0 100644 --- a/app/controllers/api/v1/IAM.controller.js +++ b/app/controllers/api/v1/IAM.controller.js @@ -155,9 +155,9 @@ class IAMController extends Controller { .message(`${req.T('common.invalid')} access_type. ${req.T('api.must_one')} allow, deny.`) .api() - if ( !['application', 'api_scope', 'machine', 'machine_group', 'vault'].includes(req.body.target_type) ) + if ( !['application', 'api_scope', 'machine', 'machine_group'].includes(req.body.target_type) ) return res.status(400) - .message(`${req.T('common.invalid')} target_type. ${req.T('api.must_one')} application, api_scope, machine, machine_group, vault.`) + .message(`${req.T('common.invalid')} target_type. ${req.T('api.must_one')} application, api_scope, machine, machine_group.`) .api() // Make sure the target_id is valid @@ -188,13 +188,6 @@ class IAMController extends Controller { return res.status(400) .message(`${req.T('common.invalid')} target_id.`) .api() - } else if ( req.body.target_type === 'vault' ) { - const Vault = this.models.get('vault:Vault') - const vault = await Vault.findById(req.body.target_id) - if ( !vault?.active || !(await Policy.check_user_access(req.user, vault.id, 'update')) ) - return res.status(400) - .message(`${req.T('common.invalid')} target_id.`) - .api() } const policy = new Policy({ @@ -237,7 +230,7 @@ class IAMController extends Controller { .api() } - const valid_target_types = ['application', 'api_scope', 'machine', 'machine_group', 'vault'] + const valid_target_types = ['application', 'api_scope', 'machine', 'machine_group'] if ( !valid_target_types.includes(req.body.target_type) ) { return res.status(400) .message(`${req.T('api.invalid_target_type')}`) @@ -319,9 +312,9 @@ class IAMController extends Controller { .message(`${req.T('common.invalid')} access_type. ${req.T('api.must_one')} allow, deny.`) .api() - if ( !['application', 'api_scope', 'machine', 'machine_group', 'vault'].includes(req.body.target_type) ) + if ( !['application', 'api_scope', 'machine', 'machine_group'].includes(req.body.target_type) ) return res.status(400) - .message(`${req.T('common.invalid')} target_type. ${req.T('api.must_one')} application, api_scope, machine, machine_group, vault.`) + .message(`${req.T('common.invalid')} target_type. ${req.T('api.must_one')} application, api_scope, machine, machine_group.`) .api() // Make sure the target_id is valid @@ -352,13 +345,6 @@ class IAMController extends Controller { return res.status(400) .message(`${req.T('common.invalid')} target_id.`) .api() - } else if ( req.body.target_type === 'vault' ) { - const Vault = this.models.get('vault:Vault') - const vault = await Vault.findById(req.body.target_id) - if ( !vault?.active || !(await Policy.check_user_access(req.user, vault.id, 'update')) ) - return res.status(400) - .message(`${req.T('common.invalid')} target_id.`) - .api() } policy.entity_type = req.body.entity_type @@ -403,7 +389,7 @@ class IAMController extends Controller { .api() } - const valid_target_types = ['application', 'api_scope', 'machine', 'machine_group', 'vault'] + const valid_target_types = ['application', 'api_scope', 'machine', 'machine_group'] if ( !valid_target_types.includes(req.body.target_type) ) { return res.status(400) .message(`${req.T('api.invalid_target_type')}`) diff --git a/app/controllers/api/v1/Vault.controller.js b/app/controllers/api/v1/Vault.controller.js deleted file mode 100644 index 1977bdd..0000000 --- a/app/controllers/api/v1/Vault.controller.js +++ /dev/null @@ -1,130 +0,0 @@ -const { Controller } = require('libflitter') - -class VaultController extends Controller { - static get services() { - return [...super.services, 'models'] - } - - async get_vaults(req, res, next) { - const Policy = this.models.get('iam:Policy') - const Vault = this.models.get('vault:Vault') - - await Vault.for_user(req.user) - - const vaults = await Vault.find({ active: true }) - console.log('found vaults', vaults) - - const accessible = [] - for ( const vault of vaults ) { - if ( await Policy.check_user_access(req.user, vault.id, 'view') ) { - accessible.push(await vault.to_api()) - } - } - - return res.api(accessible) - } - - async get_vault(req, res, next) { - const Policy = this.models.get('iam:Policy') - const Vault = this.models.get('vault:Vault') - - const vault = await Vault.findById(req.params.id) - if ( !vault?.active ) { - return res.status(404) - .message(req.T('api.vault_not_found')) - .api() - } - - if ( !(await Policy.check_user_access(req.user, vault.id, 'view')) ) { - return res.status(401) - .message(req.T('api.insufficient_permissions')) - .api() - } - - return res.api(await vault.to_api()) - } - - async create_vault(req, res, next) { - const Policy = this.models.get('iam:Policy') - const Vault = this.models.get('vault:Vault') - - if ( !req.body.name ) { - return res.status(400) - .message(`${req.T('api.missing_field')} name`) - .api() - } - - const vault = new Vault({ - name: req.body.name - }) - - await vault.save() - await vault.grant_default(req.user) - - return res.api(await vault.to_api()) - } - - async update_vault(req, res, next) { - const Policy = this.models.get('iam:Policy') - const Vault = this.models.get('vault:Vault') - - if ( !req.body.name ) { - return res.status(400) - .message(`${req.T('api.missing_field')} name`) - .api() - } - - const vault = await Vault.findById(req.params.id) - if ( !vault?.active ) { - return res.status(404) - .message(req.T('api.vault_not_found')) - .api() - } - - if ( !(await Policy.check_user_access(req.user, vault.id, 'update')) ) { - return res.status(401) - .message(req.T('api.insufficient_permissions')) - .api() - } - - vault.name = req.body.name - await vault.save() - return res.api(await vault.to_api()) - } - - async delete_vault(req, res, next) { - const Policy = this.models.get('iam:Policy') - const Vault = this.models.get('vault:Vault') - - const vault = await Vault.findById(req.params.id) - if ( !vault?.active ) { - return res.status(404) - .message(req.T('api.vault_not_found')) - .api() - } - - if ( !(await Policy.check_user_access(req.user, vault.id, 'delete')) ) { - return res.status(401) - .message(req.T('api.insufficient_permissions')) - .api() - } - - vault.active = false - await vault.save() - - const policies = await Policy.find({ - active: true, - target_type: 'vault', - target_id: vault.id, - }) - - for ( const policy of policies ) { - policy.active = false - await policy.save() - } - - return res.api() - } -} - -module.exports = exports = VaultController diff --git a/app/models/iam/Policy.model.js b/app/models/iam/Policy.model.js index 59510eb..cedc454 100644 --- a/app/models/iam/Policy.model.js +++ b/app/models/iam/Policy.model.js @@ -12,7 +12,7 @@ class PolicyModel extends Model { entity_type: String, // user | group entity_id: String, access_type: String, // allow | deny - target_type: { type: String, default: 'application' }, // application | api_scope | machine | machine_group | vault + target_type: { type: String, default: 'application' }, // application | api_scope | machine | machine_group target_id: String, active: { type: Boolean, default: true }, for_permission: { type: Boolean, default: false }, @@ -209,10 +209,6 @@ class PolicyModel extends Model { const MachineGroup = this.models.get('ldap:MachineGroup') const group = await MachineGroup.findById(this.target_id) target_display = `Computer Group: ${group.name} (${group.machine_ids.length} computers)` - } else if ( this.target_type === 'vault' ) { - const Vault = this.models.get('vault:Vault') - const vault = await Vault.findById(this.target_id) - target_display = `Vault: ${vault.name}` } return { diff --git a/app/models/vault/Entry.model.js b/app/models/vault/Entry.model.js deleted file mode 100644 index d089c34..0000000 --- a/app/models/vault/Entry.model.js +++ /dev/null @@ -1,29 +0,0 @@ -const { Model } = require('flitter-orm') - -class EntryModel extends Model { - static get services() { - return [...super.services, 'models'] - } - - static get schema() { - return { - active: { type: Boolean, default: true }, - vault_id: String, - key: String, - locked_value: String, - } - } - - async to_api() { - return { - id: this.id, - _id: this.id, - vault_id: this.vault_id, - key: this.key, - locked_value: this.locked_value, - active: this.active, - } - } -} - -module.exports = exports = EntryModel diff --git a/app/models/vault/Vault.model.js b/app/models/vault/Vault.model.js deleted file mode 100644 index 0dd0e7d..0000000 --- a/app/models/vault/Vault.model.js +++ /dev/null @@ -1,66 +0,0 @@ -const { Model } = require('flitter-orm') - -class VaultModel extends Model { - static get services() { - return [...super.services, 'models'] - } - - static get schema() { - return { - active: { type: Boolean, default: true }, - name: String, - user_id: String, - } - } - - static async for_user(user) { - const existing = await this.findOne({ - user_id: user.id, - }) - - if ( existing ) return existing - - const vault = new this({ - name: `${user.first_name} ${user.last_name}'s Vault`, - user_id: user.id, - }) - - await vault.save() - await vault.grant_default(user) - - return vault - } - - async grant_default(user) { - const Policy = this.models.get('iam:Policy') - - const grants = ['view', 'read', 'update', 'delete', undefined] - for ( const grant of grants ) { - const policy = new Policy({ - entity_type: 'user', - entity_id: user.id, - access_type: 'allow', - target_type: 'vault', - target_id: this.id, - ...(grant ? { - for_permission: true, - permission: grant - } : {}) - }) - - await policy.save() - } - } - - async to_api() { - return { - id: this.id, - _id: this.id, - name: this.name, - active: this.active, - user_id: this.user_id, - } - } -} - -module.exports = exports = VaultModel diff --git a/app/routing/routers/api/v1/vault.routes.js b/app/routing/routers/api/v1/vault.routes.js deleted file mode 100644 index 75a9599..0000000 --- a/app/routing/routers/api/v1/vault.routes.js +++ /dev/null @@ -1,41 +0,0 @@ -const iam_routes = { - prefix: '/api/v1/vault', - - middleware: [ - 'auth:APIRoute' - ], - - get: { - '/vaults': [ - ['middleware::api:Permission', { check: 'v1:vault:vaults:list' }], - 'controller::api:v1:Vault.get_vaults', - ], - '/vaults/:id': [ - ['middleware::api:Permission', { check: 'v1:vault:vaults:get' }], - 'controller::api:v1:Vault.get_vault', - ], - }, - - post: { - '/vaults': [ - ['middleware::api:Permission', { check: 'v1:vault:vaults:create' }], - 'controller::api:v1:Vault.create_vault', - ], - }, - - patch: { - '/vaults/:id': [ - ['middleware::api:Permission', { check: 'v1:vault:vaults:update' }], - 'controller::api:v1:Vault.update_vault', - ], - }, - - delete: { - '/vaults/:id': [ - ['middleware::api:Permission', { check: 'v1:vault:vaults:delete' }], - 'controller::api:v1:Vault.delete_vault', - ], - }, -} - -module.exports = exports = iam_routes diff --git a/app/unit/OpenIDConnectUnit.js b/app/unit/OpenIDConnectUnit.js index 91487b6..03d6f7d 100644 --- a/app/unit/OpenIDConnectUnit.js +++ b/app/unit/OpenIDConnectUnit.js @@ -1,3 +1,4 @@ +const fs = require('fs') const Unit = require('libflitter/Unit') const { Provider, interactionPolicy: { Prompt, base: policy } } = require('oidc-provider') const uuid = require('uuid').v4 @@ -14,6 +15,15 @@ class OpenIDConnectUnit extends Unit { return [...super.services, 'output', 'configs', 'models'] } + load_jwks(file) { + if ( fs.existsSync(file) ) { + const content = fs.readFileSync(file) + try { + return JSON.parse(content) + } catch (e) {} + } + } + async go(app) { this.Vue = this.app.di().get('Vue') const issuer = this.configs.get('app.url') @@ -23,9 +33,13 @@ class OpenIDConnectUnit extends Unit { CoreIDAdapter.connect(app) + const jwks_file = this.configs.get('oidc.jwks_file') + const jwks = this.load_jwks(jwks_file) + this.provider = new Provider(issuer, { adapter: CoreIDAdapter, clients: [], + jwks, interactions: { interactions, url: (ctx, interaction) => `/openid/interaction/${ctx.oidc.uid.toLowerCase()}`, diff --git a/config/auth.config.js b/config/auth.config.js index ca40486..e78610c 100644 --- a/config/auth.config.js +++ b/config/auth.config.js @@ -13,22 +13,6 @@ const auth_config = { target_type: 'machine_group', permission: 'sudo', }, - { - target_type: 'vault', - permission: 'view', - }, - { - target_type: 'vault', - permission: 'read', - }, - { - target_type: 'vault', - permission: 'update', - }, - { - target_type: 'vault', - permission: 'delete', - }, ], }, diff --git a/config/oidc.config.js b/config/oidc.config.js index df3f0f1..3499d69 100644 --- a/config/oidc.config.js +++ b/config/oidc.config.js @@ -1,7 +1,8 @@ const oidc_config = { provider: { proxy: env('OPENID_CONNECT_PROXY', false), - } + }, + jwks_file: env('OPENID_CONNECT_JWKS_FILE'), } module.exports = exports = oidc_config diff --git a/config/traps.config.js b/config/traps.config.js index 50a80c9..54bc1fb 100644 --- a/config/traps.config.js +++ b/config/traps.config.js @@ -38,7 +38,6 @@ const traps_config = { '/api/v1/locale/batch', '/api/v1/auth/validate/username', '/api/v1/auth/attempt', - '/api/v1/vault/get-trust-payload', ], }, verify_email: { @@ -51,7 +50,6 @@ const traps_config = { '/api/v1/locale/batch', '/api/v1/auth/validate/username', '/api/v1/auth/attempt', - '/api/v1/vault/get-trust-payload', '/auth/action/*', '/api/v1/message/banners', '/api/v1/message/banners/read/*', diff --git a/locale/en_US/api.locale.js b/locale/en_US/api.locale.js index ea9de2b..1a62e7d 100644 --- a/locale/en_US/api.locale.js +++ b/locale/en_US/api.locale.js @@ -6,7 +6,6 @@ module.exports = exports = { machine_not_found: 'Machine not found with that ID.', group_already_exists: 'A group with that name already exists.', machine_already_exists: 'A machine with that name already exists.', - vault_not_found: 'A vault with that ID not found.', user_not_found: 'User not found with that ID.', photo_not_found: 'This user has no photo.',