Apply the rules enforcer, based on origin location
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': minor
|
||||
---
|
||||
|
||||
Apply the catalog rules enforcer, based on origin location.
|
||||
|
||||
This is a breaking change, in the sense that this was not properly checked in earlier versions of the new catalog engine. You may see ingestion of certain entities start to be rejected after this update, if the following conditions apply to you:
|
||||
|
||||
- You are using the configuration key `catalog.rules.[].allow`, and
|
||||
- Your registered locations point (directly or transitively) to entities whose kinds are not listed in `catalog.rules.[].allow`
|
||||
|
||||
and/or
|
||||
|
||||
- You are using the configuration key `catalog.locations.[].rules.[].allow`
|
||||
- The config locations point (directly or transitively) to entities whose kinds are not listed neither `catalog.rules.[].allow`, nor in the corresponding `.rules.[].allow` of that config location
|
||||
|
||||
This is an example of what the configuration might look like:
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
# These do not list Template as a valid kind; users are therefore unable to
|
||||
# manually register entities of the Template kind
|
||||
rules:
|
||||
- allow:
|
||||
- Component
|
||||
- API
|
||||
- Resource
|
||||
- Group
|
||||
- User
|
||||
- System
|
||||
- Domain
|
||||
- Location
|
||||
locations:
|
||||
# This lists Template as valid only for that specific config location
|
||||
- type: file
|
||||
target: ../../plugins/scaffolder-backend/sample-templates/all-templates.yaml
|
||||
rules:
|
||||
- allow: [Template]
|
||||
```
|
||||
|
||||
If you are not using any of those `rules` section, you should not be affected by this change.
|
||||
|
||||
If you do use any of those `rules` sections, make sure that they are complete and list all of the kinds that are in active use in your Backstage installation.
|
||||
@@ -751,6 +751,7 @@ export class DefaultCatalogProcessingOrchestrator
|
||||
logger: Logger_2;
|
||||
parser: CatalogProcessorParser;
|
||||
policy: EntityPolicy;
|
||||
rulesEnforcer: CatalogRulesEnforcer;
|
||||
});
|
||||
// (undocumented)
|
||||
process(request: EntityProcessingRequest): Promise<EntityProcessingResult>;
|
||||
@@ -1497,4 +1498,5 @@ export class UrlReaderProcessor implements CatalogProcessor {
|
||||
// src/ingestion/processors/types.d.ts:58:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/ingestion/types.d.ts:17:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/ingestion/types.d.ts:41:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// src/next/processing/DefaultCatalogProcessingOrchestrator.d.ts:15:9 - (ae-forgotten-export) The symbol "CatalogRulesEnforcer" needs to be exported by the entry point index.d.ts
|
||||
```
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { LocationSpec, Entity } from '@backstage/catalog-model';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* A structure for matching entities to a given rule.
|
||||
@@ -104,7 +105,7 @@ export class CatalogRulesEnforcer {
|
||||
return [];
|
||||
}
|
||||
const type = locConf.getString('type');
|
||||
const target = locConf.getString('target');
|
||||
const target = resolveTarget(type, locConf.getString('target'));
|
||||
|
||||
return locConf.getConfigArray('rules').map(ruleConf => ({
|
||||
allow: ruleConf.getStringArray('allow').map(kind => ({ kind })),
|
||||
@@ -175,3 +176,11 @@ export class CatalogRulesEnforcer {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveTarget(type: string, target: string): string {
|
||||
if (type !== 'file') {
|
||||
return target;
|
||||
}
|
||||
|
||||
return path.resolve(target);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ import {
|
||||
import { CatalogEnvironment } from '../service/CatalogBuilder';
|
||||
import { createNextRouter } from './NextRouter';
|
||||
import { DefaultRefreshService } from './DefaultRefreshService';
|
||||
import { CatalogRulesEnforcer } from '../ingestion/CatalogRules';
|
||||
|
||||
/**
|
||||
* A builder that helps wire up all of the component parts of the catalog.
|
||||
@@ -304,9 +305,11 @@ export class NextCatalogBuilder {
|
||||
refreshInterval: this.refreshInterval,
|
||||
});
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const rulesEnforcer = CatalogRulesEnforcer.fromConfig(config);
|
||||
const orchestrator = new DefaultCatalogProcessingOrchestrator({
|
||||
processors,
|
||||
integrations,
|
||||
rulesEnforcer,
|
||||
logger,
|
||||
parser,
|
||||
policy,
|
||||
|
||||
+27
-2
@@ -21,8 +21,9 @@ import {
|
||||
LocationSpec,
|
||||
parseLocationReference,
|
||||
stringifyEntityRef,
|
||||
stringifyLocationReference,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ConflictError, InputError } from '@backstage/errors';
|
||||
import { ConflictError, InputError, NotAllowedError } from '@backstage/errors';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import path from 'path';
|
||||
import { Logger } from 'winston';
|
||||
@@ -45,6 +46,7 @@ import {
|
||||
validateEntity,
|
||||
validateEntityEnvelope,
|
||||
} from './util';
|
||||
import { CatalogRulesEnforcer } from '../../ingestion/CatalogRules';
|
||||
|
||||
type Context = {
|
||||
entityRef: string;
|
||||
@@ -63,6 +65,7 @@ export class DefaultCatalogProcessingOrchestrator
|
||||
logger: Logger;
|
||||
parser: CatalogProcessorParser;
|
||||
policy: EntityPolicy;
|
||||
rulesEnforcer: CatalogRulesEnforcer;
|
||||
},
|
||||
) {}
|
||||
|
||||
@@ -117,8 +120,30 @@ export class DefaultCatalogProcessingOrchestrator
|
||||
}
|
||||
entity = await this.runPostProcessStep(entity, context);
|
||||
|
||||
// Check that any emitted entities are permitted to originate from that
|
||||
// particular location according to the catalog rules
|
||||
const collectorResults = context.collector.results();
|
||||
for (const deferredEntity of collectorResults.deferredEntities) {
|
||||
if (
|
||||
!this.options.rulesEnforcer.isAllowed(
|
||||
deferredEntity.entity,
|
||||
context.originLocation,
|
||||
)
|
||||
) {
|
||||
throw new NotAllowedError(
|
||||
`Entity ${stringifyEntityRef(
|
||||
deferredEntity.entity,
|
||||
)} at ${stringifyLocationReference(
|
||||
context.location,
|
||||
)}, originated at ${stringifyLocationReference(
|
||||
context.originLocation,
|
||||
)}, is not of an allowed kind for that location`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...context.collector.results(),
|
||||
...collectorResults,
|
||||
completedEntity: entity,
|
||||
state: new Map(),
|
||||
ok: true,
|
||||
|
||||
Reference in New Issue
Block a user