Skip to content

Commit

Permalink
feature mapping reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
ikethecoder committed Oct 25, 2024
1 parent 6b4eb86 commit d9ebc2b
Show file tree
Hide file tree
Showing 17 changed files with 545 additions and 77 deletions.
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)
}
}
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
10 changes: 10 additions & 0 deletions src/services/report/data/features/api_directory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { GatewayService } from '../../../../services/keystone/types';
import { ReportOfNamespaces } from '../namespaces';

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

export function has_feature_consumer_mgmt(
ns: ReportOfNamespaces,
service: GatewayService,
routeName: string
): Boolean {
return (
['client-credentials', 'kong-api-key-acl'].indexOf(
service.environment?.flow
) >= 0
);
}
65 changes: 65 additions & 0 deletions src/services/report/data/features/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
GatewayPlugin,
GatewayService,
} from '../../../../services/keystone/types';
import { ReportOfNamespaces } from '../namespaces';
import { has_feature_api_directory } from './api_directory';
import { has_feature_consumer_mgmt } from './consumer_mgmt';
import { is_production } from './production';
import { has_feature_protected } from './protected';
import { has_feature_protected_externally } from './protected_externally';
import { has_feature_shared_idp } from './shared_idp';
import { has_feature_two_tiered_access } from './two_tiered_access';

export const FeatureList: { [key: string]: Function } = {
api_directory: has_feature_api_directory,
shared_idp: has_feature_shared_idp,
consumer_mgmt: has_feature_consumer_mgmt,
protected: has_feature_protected,
protected_externally: has_feature_protected_externally,
two_tiered_access: has_feature_two_tiered_access,
production: is_production,
};

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](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
);
}
13 changes: 13 additions & 0 deletions src/services/report/data/features/protected_externally.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { GatewayService } from '../../../keystone/types';
import { ReportOfNamespaces } from '../namespaces';

// The reality is that this is not a real scenario
// by definition "protected externally" means there is no use
// of the gateway and therefore should not have Gateway Services
export function has_feature_protected_externally(
ns: ReportOfNamespaces,
service: GatewayService,
routeName: string
): Boolean {
return service.environment?.flow == 'protected-externally';
}
10 changes: 10 additions & 0 deletions src/services/report/data/features/shared_idp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { GatewayService } from '../../../keystone/types';
import { ReportOfNamespaces } from '../namespaces';

export function has_feature_shared_idp(
ns: ReportOfNamespaces,
service: GatewayService,
routeName: string
): Boolean {
return Boolean(service.environment?.credentialIssuer?.inheritFrom?.name);
}
20 changes: 20 additions & 0 deletions src/services/report/data/features/two_tiered_access.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { GatewayPlugin, GatewayService } from '../../../keystone/types';
import { ReportOfNamespaces } from '../namespaces';

export function has_feature_two_tiered_access(
ns: ReportOfNamespaces,
service: GatewayService,
routeName: string
): Boolean {
return (
// check either service or route plugin
// has the "config.anonymous"
check(service.plugins) ||
service.routes.filter((r) => r.name == routeName && check(r.plugins))
.length > 0
);
}

function check(plugins: GatewayPlugin[]): boolean {
return plugins.filter((p) => (p.config as any).anonymous).length > 0;
}
Loading

0 comments on commit d9ebc2b

Please sign in to comment.