From bc8e81f751d9cf9e80389f675bf1b26bd778bfc3 Mon Sep 17 00:00:00 2001 From: dakota002 Date: Mon, 19 Aug 2024 14:58:08 -0400 Subject: [PATCH] Adds some checks to confirm actions, and include Update Broker from DB action --- app/lib/kafka.server.ts | 26 ++---- app/routes/admin.kafka._index.tsx | 147 ++++++++++++++++++++++-------- app/routes/admin.kafka.edit.tsx | 11 ++- sandbox-seed.json | 16 +++- 4 files changed, 138 insertions(+), 62 deletions(-) diff --git a/app/lib/kafka.server.ts b/app/lib/kafka.server.ts index 831aa0701..dbaa4c5b5 100644 --- a/app/lib/kafka.server.ts +++ b/app/lib/kafka.server.ts @@ -9,6 +9,7 @@ import { tables } from '@architect/functions' import { paginateScan } from '@aws-sdk/lib-dynamodb' import type { DynamoDBDocument } from '@aws-sdk/lib-dynamodb' import crypto from 'crypto' +import type { AclFilter } from 'gcn-kafka' import { Kafka } from 'gcn-kafka' import type { AclEntry } from 'kafkajs' import { @@ -81,17 +82,6 @@ if (process.env.ARC_SANDBOX) { } } -/** - * AclEntry already contains definitions for the following: - * - * principal: string --> 'User:{cognito_group_name}' - * host: string --> '*' - * operation: AclOperationTypes --> Read,Write, etc from enum - * permissionType: AclPermissionTypes --> Allow, Deny, etc from enum - * resourceType: AclResourceTypes --> TOPIC, etc - * resourceName: string --> name of topic: 'gcn.notices.burstcube' - * resourcePatternType: ResourcePatternTypes --> PREFIXED or LITERAL - */ export type KafkaACL = AclEntry & { aclId?: string } @@ -139,9 +129,9 @@ export async function createKafkaACL( resourceName, principal: `User:${group}`, host: '*', - operation, // Read, write, etc - permissionType, // Allow, deny etc - resourcePatternType: 3, // LITERAL | PREFIXED + operation, + permissionType, + resourcePatternType: 3, resourceType, } }) @@ -150,9 +140,9 @@ export async function createKafkaACL( resourceName, principal: `User:${group}`, host: '*', - operation, // Read, write, etc + operation, permissionType, - resourcePatternType: 3, // LITERAL | PREFIX + resourcePatternType: 3, resourceType, } }) @@ -280,13 +270,13 @@ export async function getAclsFromBrokers() { export async function deleteKafkaACL(user: User, aclIds: string[]) { validateUser(user) const db = await tables() - const acls = await Promise.all( + const acls: KafkaACL[] = await Promise.all( aclIds.map((aclId) => db.kafka_acls.get({ aclId })) ) const adminClient = adminKafka.admin() await adminClient.connect() - await adminClient.deleteAcls({ filters: acls }) + await adminClient.deleteAcls({ filters: acls as AclFilter[] }) await adminClient.disconnect() await Promise.all( diff --git a/app/routes/admin.kafka._index.tsx b/app/routes/admin.kafka._index.tsx index b2b877679..0685fcd23 100644 --- a/app/routes/admin.kafka._index.tsx +++ b/app/routes/admin.kafka._index.tsx @@ -19,6 +19,7 @@ import { ModalToggleButton, TextInput, } from '@trussworks/react-uswds' +import { groupBy, sortBy } from 'lodash' import { useEffect, useRef, useState } from 'react' import { getUser } from './_auth/user.server' @@ -43,7 +44,13 @@ export async function loader({ request }: LoaderFunctionArgs) { if (!user || !user.groups.includes(adminGroup)) throw new Response(null, { status: 403 }) const { aclFilter } = Object.fromEntries(new URL(request.url).searchParams) - const dynamoDbAclData = await getKafkaACLsFromDynamoDB(user, aclFilter) + const dynamoDbAclData = groupBy( + sortBy(await getKafkaACLsFromDynamoDB(user, aclFilter), [ + 'resourceName', + 'principal', + ]), + 'resourceName' + ) const latestSync = await getLastSyncDate() return { dynamoDbAclData, latestSync } } @@ -54,6 +61,7 @@ export async function action({ request }: ActionFunctionArgs) { throw new Response(null, { status: 403 }) const data = await request.formData() const intent = getFormDataString(data, 'intent') + if (intent === 'migrateFromBroker') { await updateDbFromBrokers(user) return null @@ -64,11 +72,11 @@ export async function action({ request }: ActionFunctionArgs) { return null } - const aclId = getFormDataString(data, 'aclId') const promises = [] switch (intent) { case 'delete': + const aclId = getFormDataString(data, 'aclId') if (!aclId) throw new Response(null, { status: 400 }) promises.push(deleteKafkaACL(user, [aclId])) break @@ -78,8 +86,8 @@ export async function action({ request }: ActionFunctionArgs) { data, 'userClientType' ) as UserClientType - const permissionTypeString = getFormDataString(data, 'permissionType') const group = getFormDataString(data, 'group') + const permissionTypeString = getFormDataString(data, 'permissionType') const includePrefixed = getFormDataString(data, 'includePrefixed') const resourceTypeString = getFormDataString(data, 'resourceType') @@ -122,6 +130,7 @@ export default function Index() { const updateFetcher = useFetcher() const aclFetcher = useFetcher() const brokerFromDbFetcher = useFetcher() + const ref = useRef(null) useEffect(() => { setAclData(aclFetcher.data?.dynamoDbAclData ?? aclData) @@ -140,7 +149,6 @@ export default function Index() { data from topics, create or delete topics, manage consumer groups, and perform administrative tasks.

- - {brokerFromDbFetcher.state !== 'idle' && ( - - Updating... - - )} - - {aclData && ( <> - + + + + ) } @@ -221,26 +264,56 @@ function KafkaAclCard({ acl }: { acl: KafkaACL }) { const fetcher = useFetcher() const disabled = fetcher.state !== 'idle' + // TODO: These maps can probably be refactored, since they are + // just inverting the enum from kafka, but importing them + // directly here causes some errors. Same for mapping them to + // dropdowns const permissionMap: { [key: number]: string } = { 2: 'Deny', 3: 'Allow', } + const operationMap: { [key: number]: string } = { + 0: 'Unknown', + 1: 'Any', + 2: 'All', + 3: 'Read', + 4: 'Write', + 5: 'Create', + 6: 'Delete', + 7: 'Alter', + 8: 'Describe', + 9: 'Cluster Action', + 10: 'Describe Configs', + 11: 'Alter Configs', + 12: 'Idempotent Write', + } + + const resourceTypeMap: { [key: number]: string } = { + 0: 'Unknown', + 1: 'Any', + 2: 'Topic', + 3: 'Group', + 4: 'Cluster', + 5: 'Transactional Id', + 6: 'Delegation Token', + } + return ( <> -
+
+
+ Type: {resourceTypeMap[acl.resourceType]} +
Group: {acl.principal}
- {/*
- Client Type: {acl.userClientType} -
*/}
Permission: {permissionMap[acl.permissionType]}
- Resource: {acl.resourceName} + Operation: {operationMap[acl.operation]}
@@ -272,8 +345,8 @@ function KafkaAclCard({ acl }: { acl: KafkaACL }) { Delete Kafka ACL diff --git a/app/routes/admin.kafka.edit.tsx b/app/routes/admin.kafka.edit.tsx index af8aad19a..f3f3c6887 100644 --- a/app/routes/admin.kafka.edit.tsx +++ b/app/routes/admin.kafka.edit.tsx @@ -39,20 +39,25 @@ function KafkaAclForm({ groups }: { groups: string[] }) {

Create Kafka ACLs

+ +