Skip to content
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

Feat/move apps to broker #49

Merged
merged 3 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/vault-config-intention.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"event": {
"provider": "vsync-github-action",
"reason": "Job triggered",
"transient": true,
"url": "JOB_URL"
},
"actions": [
Expand Down
38,677 changes: 33,402 additions & 5,275 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,26 @@
"dependencies": {
"@oclif/command": "^1.8.36",
"@oclif/config": "^1.18.17",
"@oclif/plugin-help": "^6.0.14",
"@oclif/core": "^3.25.3",
"@oclif/plugin-help": "^6.0.18",
"@types/ejs": "^3.1.5",
"axios": "^1.6.7",
"axios": "^1.6.8",
"ejs": "^3.1.9",
"inversify": "^6.0.2",
"merge-deep": "^3.0.3",
"node-vault": "^0.10.2",
"reflect-metadata": "^0.2.1",
"tslib": "^2.6.2",
"winston": "^3.11.0"
"winston": "^3.12.0"
},
"devDependencies": {
"@oclif/dev-cli": "^1.26.10",
"@types/jest": "^29.5.12",
"@types/merge-deep": "^3.0.3",
"@types/node": "^20.11.20",
"@types/node": "^20.11.26",
"@types/request": "^2.48.12",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^27.9.0",
Expand Down
1 change: 1 addition & 0 deletions src/broker/broker.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export class BrokerApi {
app: service.name,
project: project.name,
env: service.shortEnv,
config: service.vaultConfig,
});
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/broker/dto/service-rest.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ export class ServiceRestDto extends VertexPointerRestDto {
description?: string;
name!: string;
title?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
vaultConfig?: any;
}
12 changes: 0 additions & 12 deletions src/services/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ export interface GroupConfig {
}

export interface VaultConfig {
/** Application configuration */
apps: AppConfig[];
/** Per-environment defaults for policies to grant each type of actor */
appActorDefaults: AppActorPolicies;
/** Array of database secret engines. */
Expand All @@ -84,16 +82,6 @@ export interface VaultConfig {
* Service for configuration details
*/
export interface ConfigService {
/**
* Return all applications in the configuration
*/
getApps(): Promise<AppConfig[]>;

/**
* Return single applications in the configuration
*/
getApp(appName: string): Promise<AppConfig | undefined>;

/**
* Return default policies to grant each type of actor
*/
Expand Down
94 changes: 65 additions & 29 deletions src/services/impl/app-broker.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import { Application, AppService } from '../app.service';
import { AppConfig, ConfigService } from '../config.service';
import { BrokerApi } from '../../broker/broker.api';
import { TYPES } from '../../inversify.types';
import merge from 'merge-deep';

const periodLookup = {
hourly: 3600,
bidaily: 43200,
daily: 86400,
weekly: 604800,
};

@injectable()
/**
Expand All @@ -22,51 +30,79 @@ export class AppBrokerService implements AppService {
* Gets all apps
*/
public async getAllApps(): Promise<Application[]> {
const appConfigArr = await this.config.getApps();
const appConfigObj = appConfigArr.reduce<{ [key: string]: AppConfig }>(
(o, config) => ({ ...o, [config.name]: config }),
{},
);

const applications = await this.brokerApi.getProjectServicesAsApps();
const decoratedApps = applications
.filter(
(app: Application) =>
app.app in appConfigObj && appConfigObj[app.app].enabled,
)
.map((app: Application) => this.decorateApp(app, appConfigObj[app.app]));

if (decoratedApps.length !== appConfigArr.length) {
throw new Error(
`Configured app(s) could not be found. Check config. App names should be lowercase.`,
);
}
.filter((app: Application) => app.config?.enabled)
.map((app: Application) => {
if (app.config) {
app.config = AppBrokerService.applyAppConfigDefaults(app.config);
}
return app;
});
return decoratedApps;
}

/**
* Gets a specific app
*/
public async getApp(appName: string): Promise<Application> {
const appConfig = await this.config.getApp(appName);
const applications = await this.brokerApi.getProjectServicesAsApps();
const app = applications.find((app: Application) => app.app === appName);
if (app && appConfig && appConfig.enabled) {
return this.decorateApp(app, appConfig);
if (app?.config?.enabled) {
app.config = AppBrokerService.applyAppConfigDefaults(app.config);
return app;
}
throw new Error(`App '${appName}' does not exist or is not enabled`);
}

/**
* Decorates the application info with config information.
* @param app The application info
* @param appConfig The application config
* @returns The decorated application info
* Apply configuration defaults to the app
* @param app The application config to apply defaults to
*/
private decorateApp(app: Application, appConfig: AppConfig): Application {
return {
...app,
config: appConfig,
};
private static applyAppConfigDefaults(app: AppConfig): AppConfig {
const tokenPeriodDefault =
app.policyOptions?.tokenPeriod &&
periodLookup[app.policyOptions?.tokenPeriod]
? periodLookup[app.policyOptions?.tokenPeriod]
: periodLookup['daily'];
/* eslint-disable camelcase -- Library code style issue */
return merge(
{
approle: {
// Vault defaults -- https://www.vaultproject.io/api/auth/approle
...{
enabled: false,
bind_secret_id: true,
secret_id_bound_cidrs: '',
secret_id_num_uses: 0,
secret_id_ttl: 0,
enable_local_secret_ids: false,
token_ttl: 0,
token_max_ttl: 0,
token_policies: '',
token_bound_cidrs: '',
token_explicit_max_ttl: 0,
token_no_default_policy: false,
token_num_uses: 0,
token_period: 0,
token_type: '',
},
// VS defaults
...{
secret_id_ttl: periodLookup['hourly'],
token_period: tokenPeriodDefault,
secret_id_num_uses: 1,
options: {
project: false,
read: true,
write: false,
},
role_name: '',
},
},
},
app,
);
/* eslint-enable camelcase */
}
}
7 changes: 0 additions & 7 deletions src/services/impl/config-broker.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { injectable } from 'inversify';
import {
AppActorPolicies,
AppConfig,
ConfigService,
DbConfig,
GroupConfig,
Expand All @@ -23,12 +22,6 @@ export class ConfigBrokerService implements ConfigService {
private config: ConfigFileService,
) {}

async getApps(): Promise<AppConfig[]> {
return this.config.getApps();
}
async getApp(appName: string): Promise<AppConfig | undefined> {
return this.config.getApp(appName);
}
getAppActorDefaults(): Promise<AppActorPolicies> {
return this.config.getAppActorDefaults();
}
Expand Down
28 changes: 0 additions & 28 deletions src/services/impl/config-file.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ jest.mock('fs');
const mockFs = jest.mocked(fs);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockConfig: VaultConfig = {
apps: [{ name: 'APP-TUS', enabled: true }],
appActorDefaults: {
approle: {
dev: ['project-kv-read', 'project-kv-write'],
Expand All @@ -27,9 +26,6 @@ const mockConfig: VaultConfig = {
],
};
mockFs.readFileSync.mockReturnValue(JSON.stringify(mockConfig));
// For purposes of testing, we just care that it exists
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
mockConfig.apps[0].approle = expect.anything();

import { ConfigFileService } from './config-file.service';

Expand All @@ -46,30 +42,6 @@ describe('config-file.service', () => {
expect(mockFs.readFileSync).toHaveBeenCalledTimes(1);
});

it('getApp', async () => {
// Test command
const cfs = new ConfigFileService();
const rVal = await cfs.getApp('APP-TUS');

expect(rVal).toEqual(mockConfig.apps[0]);
});

it('getApp - Unknown', async () => {
// Test command
const cfs = new ConfigFileService();
const rVal = await cfs.getApp('APP-UNK');

expect(rVal).toEqual(undefined);
});

it('getApps', async () => {
// Test command
const cfs = new ConfigFileService();
const rVal = await cfs.getApps();

expect(rVal).toEqual(mockConfig.apps);
});

it('getAppActorDefaults', async () => {
// Test command
const cfs = new ConfigFileService();
Expand Down
83 changes: 0 additions & 83 deletions src/services/impl/config-file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,11 @@ import * as path from 'path';
import { injectable } from 'inversify';
import {
AppActorPolicies,
AppConfig,
ConfigService,
DbConfig,
GroupConfig,
VaultConfig,
} from '../config.service';
import merge from 'merge-deep';

const periodLookup = {
hourly: 3600,
bidaily: 43200,
daily: 86400,
weekly: 604800,
};

@injectable()
/**
Expand All @@ -32,80 +23,6 @@ export class ConfigFileService implements ConfigService {
fs.readFileSync(ConfigFileService.policyFilePath, { encoding: 'utf8' }),
) as VaultConfig;

/**
* Apply configuration defaults to the app
* @param app The application config to apply defaults to
*/
private static applyAppConfigDefaults(app: AppConfig): AppConfig {
const tokenPeriodDefault =
app.policyOptions?.tokenPeriod &&
periodLookup[app.policyOptions?.tokenPeriod]
? periodLookup[app.policyOptions?.tokenPeriod]
: periodLookup['daily'];
/* eslint-disable camelcase -- Library code style issue */
return merge(
{
approle: {
// Vault defaults -- https://www.vaultproject.io/api/auth/approle
...{
enabled: false,
bind_secret_id: true,
secret_id_bound_cidrs: '',
secret_id_num_uses: 0,
secret_id_ttl: 0,
enable_local_secret_ids: false,
token_ttl: 0,
token_max_ttl: 0,
token_policies: '',
token_bound_cidrs: '',
token_explicit_max_ttl: 0,
token_no_default_policy: false,
token_num_uses: 0,
token_period: 0,
token_type: '',
},
// VS defaults
...{
secret_id_ttl: periodLookup['hourly'],
token_period: tokenPeriodDefault,
secret_id_num_uses: 1,
options: {
project: false,
read: true,
write: false,
},
role_name: '',
},
},
},
app,
);
/* eslint-enable camelcase */
}

/**
* Return all applications in the configuration
*/
getApps(): Promise<AppConfig[]> {
return Promise.resolve(
ConfigFileService.config.apps.map((app) =>
ConfigFileService.applyAppConfigDefaults(app),
),
);
}

/**
* Return single applications in the configuration
*/
getApp(appName: string): Promise<AppConfig | undefined> {
const app = ConfigFileService.config.apps.find(
(app) => app.name === appName,
);
return Promise.resolve(
app ? ConfigFileService.applyAppConfigDefaults(app) : app,
);
}

/**
* Return default policies to grant each type of actor
*/
Expand Down
12 changes: 4 additions & 8 deletions src/vault/policy-roots/impl/system-policy.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,12 @@ export class SystemPolicyService implements PolicyRootService<undefined> {
}

private async restrictedBrokerAppPaths(): Promise<string[]> {
const brokerApps = (await this.config.getApps())
.filter((app) => app.approle)
.filter((app) => app.brokerGlobal);
const brokerApps = (await this.appService.getAllApps())
.filter((app) => app.config?.approle)
.filter((app) => app.config?.brokerGlobal);
const paths: string[] = [];
for (const app of brokerApps) {
paths.push(
`${(await this.appService.getApp(app.name)).project.toLowerCase()}_${
app.name
}_*`,
);
paths.push(`${app.project.toLowerCase()}_${app.app}_*`);
}
return paths;
}
Expand Down
Loading