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

feature mapping reporting #1186

Draft
wants to merge 4 commits into
base: dev
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions src/services/keycloak/client-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ClientScopeRepresentation from '@keycloak/keycloak-admin-client/lib/defs/
import CertificateRepresentation from '@keycloak/keycloak-admin-client/lib/defs/certificateRepresentation';
import RoleRepresentation from '@keycloak/keycloak-admin-client/lib/defs/roleRepresentation';
import ClientRepresentation from '@keycloak/keycloak-admin-client/lib/defs/clientRepresentation';
import ProtocolMapperRepresentation from '@keycloak/keycloak-admin-client/lib/defs/protocolMapperRepresentation';

const logger = Logger('kc.client');

Expand Down Expand Up @@ -207,4 +208,8 @@ export class KeycloakClientService {
roles
);
}

public async updateClient (id: string, mapperId: string, payload: ProtocolMapperRepresentation) {
await this.kcAdminClient.clients.updateProtocolMapper({id, mapperId}, payload)
}
}
1 change: 1 addition & 0 deletions src/services/keystone/access-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export async function getAccessRequestsByNamespace(
requestor {
name
username
providerUsername
}
application {
name
Expand Down
67 changes: 64 additions & 3 deletions src/services/keystone/gateway-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,73 @@ export async function lookupServicesByNamespace(
});
logger.debug('Query result %j', result);
result.data.allGatewayServices.forEach((svc: GatewayService) => {
svc.plugins?.map((plugin) => (plugin.config = JSON.parse(plugin.config)));
svc.routes?.map((route) =>
svc.plugins?.forEach(
(plugin) => (plugin.config = JSON.parse(plugin.config))
);
svc.routes?.forEach((route) => {
route.plugins?.map(
(plugin) => (plugin.config = JSON.parse(plugin.config))
)
);
});
});
return result.data.allGatewayServices;
}

export async function lookupServicesByNamespaceForReporting(
context: any,
ns: string
): Promise<GatewayService[]> {
const result = await context.executeGraphQL({
query: `query GetServicesForReporting($ns: String!) {
allGatewayServices(where: {namespace: $ns}) {
name
host
plugins {
name
config
}
routes {
name
methods
hosts
paths
plugins {
name
config
}
}
environment {
name
active
appId
flow
credentialIssuer {
inheritFrom {
name
}
}
product {
name
}
}
}
}`,
variables: { ns: ns },
});
logger.debug(
'[lookupServicesByNamespaceForReporting] Query result %j',
result
);
result.data.allGatewayServices.forEach((svc: GatewayService) => {
svc.plugins?.forEach(
(plugin) => (plugin.config = JSON.parse(plugin.config))
);
svc.routes?.forEach((route) => {
route.hosts = JSON.parse(route.hosts);
route.plugins?.map(
(plugin) => (plugin.config = JSON.parse(plugin.config))
);
});
});
return result.data.allGatewayServices;
}
21 changes: 14 additions & 7 deletions src/services/keystone/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import format from 'date-fns/format';
import times from 'lodash/times';
import sum from 'lodash/sum';
import formatISO from 'date-fns/formatISO';
import { strictEqual } from 'assert';

interface DailyDatum {
day: string;
Expand All @@ -21,13 +22,13 @@ interface DailyDatum {
const logger = Logger('keystone.metrics');

const getServiceMetricsQuery = gql`
query GetServiceMetrics($service: String!, $days: [String!]) {
query GetServiceMetrics($services: [String]!, $days: [String!]) {
allMetrics(
sortBy: day_ASC
where: {
query: "kong_http_requests_hourly_service"
day_in: $days
service: { name_contains: $service }
service: { name_in: $services }
}
) {
query
Expand Down Expand Up @@ -82,17 +83,23 @@ const getAllConsumerDailyMetricsQuery = gql`

export async function getServiceMetrics(
context: any,
service: string,
services: string[],
days: string[]
): Promise<Metric[]> {
strictEqual(services.length > 0, true);

const result = await context.executeGraphQL({
query: getServiceMetricsQuery,
variables: { service, days },
variables: { services, days },
});

if (result.errors) {
logger.error('[getServiceMetrics] %j', result.errors);
}
logger.debug(
'[getServiceMetrics] (%s) result row count %d',
service,
result.data.allMetrics.length
'[getServiceMetrics] (%j) result row count %d',
services,
result.data.allMetrics?.length ?? 0
);
return result.data.allMetrics;
}
Expand Down
9 changes: 9 additions & 0 deletions src/services/keystone/product-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,20 @@ export async function lookupEnvironmentsByNS(
appId
name
flow
active
additionalDetailsToRequest
approval
services {
name
}
product {
id
name
dataset {
name
title
isInCatalog
}
}
legal {
reference
Expand Down
4 changes: 2 additions & 2 deletions src/services/report/data/consumer-requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ReportOfNamespaces } from './namespaces';
import { ReportOfGatewayMetrics } from './gateway-metrics';
import { getAccessRequestsByNamespace } from '../../keystone';

interface ReportOfConsumerRequest {
export interface ReportOfConsumerRequest {
namespace: string;
displayName?: string;
prod_name: string;
Expand Down Expand Up @@ -44,7 +44,7 @@ export async function getConsumerRequests(
prod_env_flow: req.productEnvironment?.flow,
app_name: req.application.name,
app_id: req.application.appId,
requestor: req.requestor.name,
requestor: req.requestor.name ?? req.requestor.providerUsername,
req_created: req.createdAt,
req_reviewer: '',
req_result: req.isComplete
Expand Down
18 changes: 18 additions & 0 deletions src/services/report/data/features/api_directory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { GatewayService } from '../../../../services/keystone/types';
import { ReportOfNamespaces } from '../namespaces';
import { ReportOfProducts } from '../products';

export function has_feature_api_directory_for_product(
ns: ReportOfNamespaces,
product: ReportOfProducts
): Boolean {
return product.prod_env_active === 'Y';
}

export function has_feature_api_directory_for_service(
ns: ReportOfNamespaces,
service: GatewayService,
routeName: string
): Boolean {
return Boolean(service.environment?.active);
}
13 changes: 13 additions & 0 deletions src/services/report/data/features/consumer_mgmt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { GatewayService } from '../../../keystone/types';
import { ReportOfNamespaces } from '../namespaces';
import { ReportOfProducts } from '../products';

export function has_feature_consumer_mgmt(
ns: ReportOfNamespaces,
product: ReportOfProducts
): Boolean {
return Boolean(
['client-credentials', 'kong-api-key-acl'].indexOf(product.prod_env_flow) >=
0 && product.prod_env_active === 'Y'
);
}
10 changes: 10 additions & 0 deletions src/services/report/data/features/dataset_in_catalog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { GatewayService } from '../../../keystone/types';
import { ReportOfNamespaces } from '../namespaces';
import { ReportOfProducts } from '../products';

export function has_feature_dataset_in_catalog(
ns: ReportOfNamespaces,
product: ReportOfProducts
): Boolean {
return product.dataset_in_catalog;
}
12 changes: 12 additions & 0 deletions src/services/report/data/features/has_gateway_mgmt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { GatewayService } from '../../../keystone/types';
import { ReportOfNamespaces } from '../namespaces';

// If there is a Route, then can consider gateway
// management being used
export function has_gateway_mgmt(
ns: ReportOfNamespaces,
service: GatewayService,
routeName: string
): Boolean {
return Boolean(true);
}
79 changes: 79 additions & 0 deletions src/services/report/data/features/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
GatewayPlugin,
GatewayService,
} from '../../../../services/keystone/types';
import { ReportOfNamespaces } from '../namespaces';
import {
has_feature_api_directory_for_product,
has_feature_api_directory_for_service,
} from './api_directory';
import { has_feature_consumer_mgmt } from './consumer_mgmt';
import { has_feature_dataset_in_catalog } from './dataset_in_catalog';
import { has_gateway_mgmt } from './has_gateway_mgmt';
import { is_production } from './production';
import { has_feature_protected } from './protected';
import { has_feature_protected_externally } from './protected_exterrnally';
import { has_feature_shared_idp } from './shared_idp';
import { has_feature_two_tiered_access } from './two_tiered_access';

export const ProductFeatureList: { [key: string]: Function } = {
consumer_mgmt: has_feature_consumer_mgmt,
protected_externally: has_feature_protected_externally,
dataset_in_catalog: has_feature_dataset_in_catalog,
api_directory: has_feature_api_directory_for_product,
};

export const FeatureList: { [key: string]: Function } = {
api_directory: has_feature_api_directory_for_service, // evaluated at a Gateway level
shared_idp: has_feature_shared_idp,
gateway_mgmt: has_gateway_mgmt,
consumer_mgmt: undefined, // evaluated at a Gateway level
protected: has_feature_protected,
two_tiered_access: has_feature_two_tiered_access,
production: is_production,
protected_externally: undefined, // evaluated at a Gateway level
dataset_in_catalog: undefined, // evaluated at a Gateway level
};

export function getFeatures(
ns: ReportOfNamespaces,
services: GatewayService[],
routeName: string
): string[] {
const service = findService(services, routeName);
const features: string[] = [];
Object.entries(FeatureList).forEach((func) => {
if (func[1] && func[1](ns, service, routeName)) {
features.push(func[0]);
}
});
return features;
}

export function getPlugins(
ns: ReportOfNamespaces,
services: GatewayService[],
routeName: string
): string[] {
const plugins: string[] = [];
const service = findService(services, routeName);

plugins.push.apply(plugins, getPluginNames(service.plugins));
service.routes.forEach((route) => {
plugins.push.apply(plugins, getPluginNames(route.plugins));
});
return [...new Set(plugins)].sort();
}

function findService(
services: GatewayService[],
routeName: string
): GatewayService {
return services
.filter((s) => s.routes.filter((r) => r.name == routeName).length > 0)
.pop();
}

function getPluginNames(plugins: GatewayPlugin[]): string[] {
return plugins?.map((p) => p.name);
}
23 changes: 23 additions & 0 deletions src/services/report/data/features/production.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { GatewayService } from '../../../keystone/types';
import { ReportOfNamespaces } from '../namespaces';

const re = /(dev.|test.|tst.|dlv.|delivery.|-dev|-test|-d.|-t.).*$/;

export function is_production(
ns: ReportOfNamespaces,
service: GatewayService,
routeName: string
): Boolean {
return (
service.routes.filter(
(r) =>
r.name == routeName &&
(r.hosts as any).filter((h: string) => checkNonProd(h) == false)
.length > 0
).length > 0
);
}

function checkNonProd(host: string) {
return re.test(host);
}
25 changes: 25 additions & 0 deletions src/services/report/data/features/protected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { GatewayPlugin, GatewayService } from '../../../keystone/types';
import { ReportOfNamespaces } from '../namespaces';

export function has_feature_protected(
ns: ReportOfNamespaces,
service: GatewayService,
routeName: string
): Boolean {
return (
// check either a `jwt-keycloak`, `oidc` or `acl`
// plugins exists and is active
check(service.plugins) ||
service.routes.filter((r) => r.name == routeName && check(r.plugins))
.length > 0
);
}

function check(plugins: GatewayPlugin[]): boolean {
return (
plugins
// .filter((p: any) => p.enabled)
.filter((p: any) => ['jwt-keycloak', 'oidc', 'acl'].indexOf(p.name) >= 0)
.length > 0
);
}
10 changes: 10 additions & 0 deletions src/services/report/data/features/protected_exterrnally.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { GatewayService } from '../../../keystone/types';
import { ReportOfNamespaces } from '../namespaces';
import { ReportOfProducts } from '../products';

export function has_feature_protected_externally(
ns: ReportOfNamespaces,
product: ReportOfProducts
): Boolean {
return 'protected-externally' === product.prod_env_flow;
}
Loading
Loading