diff --git a/src/audit/audit.service.ts b/src/audit/audit.service.ts index bf98470c..c3e48d4a 100644 --- a/src/audit/audit.service.ts +++ b/src/audit/audit.service.ts @@ -7,9 +7,9 @@ import snakecaseKeys from 'snakecase-keys'; import { ActionDto } from '../intention/dto/action.dto'; import { IntentionDto } from '../intention/dto/intention.dto'; import { AuditStreamerService } from './audit-streamer.service'; -import { UserDto } from '../intention/dto/user.dto'; import { EdgeDto } from '../persistence/dto/edge.dto'; import { EdgeInsertDto } from '../persistence/dto/edge-rest.dto'; +import { UserDto } from '../intention/dto/user.dto'; import { VertexInsertDto } from '../persistence/dto/vertex-rest.dto'; import { VertexDto } from '../persistence/dto/vertex.dto'; import { UserRolesDto } from '../collection/dto/user-roles.dto'; @@ -448,6 +448,64 @@ export class AuditService { }); } + /** + * Records authorization events in the audit activity log + * @param req The initiating http request + * @param type Indicates if this is the start or end + * @param outcome The outcome of the authorization + */ + public recordToolsSync( + type: 'start' | 'end' | 'info', + outcome: 'success' | 'failure' | 'unknown', + message: string, + project?: string, + service?: string, + failure?: HttpException, + ) { + from([ + { + message, + event: { + action: 'sync-tools', + category: 'configuration', + dataset: 'broker.audit', + kind: 'event', + type, + outcome, + }, + ...(project + ? { + labels: { + target_project: project, + }, + } + : {}), + ...(service + ? { + service: { + target: { + name: service, + environment: 'tools', + }, + }, + } + : {}), + }, + ]) + .pipe( + map(this.addEcsFunc), + map(this.addHostFunc), + map(this.addLabelsFunc), + map(this.addErrorFunc(failure)), + map(this.addMetadataActivityFunc()), + map(this.addServiceFunc), + map(this.addTimestampFunc()), + ) + .subscribe((ecsObj) => { + this.stream.putRecord(ecsObj); + }); + } + /** * Records http access to the generic access log * @param req The initiating http request diff --git a/src/collection/account.service.ts b/src/collection/account.service.ts index f8ef7d18..26826e29 100644 --- a/src/collection/account.service.ts +++ b/src/collection/account.service.ts @@ -4,6 +4,7 @@ import { BadRequestException, NotFoundException, ServiceUnavailableException, + HttpException, } from '@nestjs/common'; import { Request } from 'express'; import { Cron, CronExpression } from '@nestjs/schedule'; @@ -195,7 +196,11 @@ export class AccountService { await this.addTokenToAccountServices(token, account); } if (this.githubService.isEnabled()) { - await this.refresh(account.id.toString()); + try { + await this.refresh(account.id.toString()); + } catch (error) { + // Continue - this.refresh() audit failures + } } this.auditService.recordAccountTokenLifecycle( req, @@ -316,10 +321,23 @@ export class AccountService { id, ); + this.auditService.recordToolsSync( + 'info', + 'unknown', + `Sync broker account (${account.clientId})`, + ); + if (!account) { + const message = `Account with ID ${id} not found`; + this.auditService.recordToolsSync('info', 'failure', message); throw new NotFoundException(`Account with ID ${id} not found`); } if (!this.githubService.isEnabled()) { + this.auditService.recordToolsSync( + 'info', + 'failure', + 'Github is not setup', + ); throw new ServiceUnavailableException(); } const downstreamServices = @@ -338,14 +356,50 @@ export class AccountService { null, ); const projectName = projectDtoArr[0].collection.name; - await this.githubService.refresh( - projectName, - serviceName, - service.collection.scmUrl, - ); + try { + this.auditService.recordToolsSync( + 'start', + 'unknown', + `Start sync: service.collection.scmUrl`, + projectName, + serviceName, + ); + await this.githubService.refresh( + projectName, + serviceName, + service.collection.scmUrl, + ); + this.auditService.recordToolsSync( + 'end', + 'success', + `End sync: service.collection.scmUrl`, + projectName, + serviceName, + ); + } catch (error) { + let httpErr = error; + if (!(httpErr instanceof HttpException)) { + httpErr = new BadRequestException({ + message: error.message, + }); + } + this.auditService.recordToolsSync( + 'end', + 'failure', + `End sync: service.collection.scmUrl`, + projectName, + serviceName, + httpErr, + ); + throw httpErr; + } } } else { - // console.log('No services associated with this broker account'); + this.auditService.recordToolsSync( + 'info', + 'unknown', + `No services associated with this broker account (${account.clientId})`, + ); } } diff --git a/src/github/github.service.ts b/src/github/github.service.ts index 7e5f99b5..fafb9a82 100644 --- a/src/github/github.service.ts +++ b/src/github/github.service.ts @@ -29,7 +29,10 @@ export class GithubService { public async refresh(project: string, service: string, scmUrl: string) { if (!this.isEnabled()) { - throw new Error(); + throw new Error('Not enabled'); + } + if (!scmUrl) { + throw new Error('Service does not have Github repo URL to update'); } const path = `tools/${project}/${service}`; const kvData = await lastValueFrom( @@ -37,14 +40,7 @@ export class GithubService { ); if (kvData) { for (const [secretName, secretValue] of Object.entries(kvData)) { - if (scmUrl) { - await this.updateSecret(scmUrl, secretName, secretValue.toString()); - } else { - console.log( - 'Service does not have Github repo URL to update:', - service, - ); - } + await this.updateSecret(scmUrl, secretName, secretValue.toString()); } } } @@ -58,43 +54,32 @@ export class GithubService { const token = await this.getInstallationAccessToken(owner, repo); const filteredSecretName = secretName.replace(/[^a-zA-Z0-9_]/g, '_'); - try { - if (token) { - const { key: base64PublicKey, key_id: keyId } = await this.getPublicKey( - owner, - repo, - token, - ); - // Encrypt secret - const encryptedSecret = await this.encryptSecret( - base64PublicKey.toString('utf-8'), - secretValue, - ); - // Update secret - await this.axiosInstance.put( - `/repos/${owner}/${repo}/actions/secrets/${filteredSecretName}`, - { - encrypted_value: encryptedSecret, - key_id: keyId, - }, - { - headers: { - Authorization: `token ${token}`, - }, - }, - ); - console.log( - `Secret ${filteredSecretName} updated successfully on ${owner}/${repo}!`, - ); - } else { - console.log( - `Github access token is null! No updates on ${owner}/${repo}`, - ); - } - } catch (error) { - console.error('Errors on updating broker JWT with API calls', error); - //throw new Error('Failed to update secret in github repo.'); + if (!token) { + throw new Error('Github access token is null!'); } + const { key: base64PublicKey, key_id: keyId } = await this.getPublicKey( + owner, + repo, + token, + ); + // Encrypt secret + const encryptedSecret = await this.encryptSecret( + base64PublicKey.toString('utf-8'), + secretValue, + ); + // Update secret + await this.axiosInstance.put( + `/repos/${owner}/${repo}/actions/secrets/${filteredSecretName}`, + { + encrypted_value: encryptedSecret, + key_id: keyId, + }, + { + headers: { + Authorization: `token ${token}`, + }, + }, + ); } // Generate JWT diff --git a/src/vault/vault.service.ts b/src/vault/vault.service.ts index 27f3502e..7ac182c2 100644 --- a/src/vault/vault.service.ts +++ b/src/vault/vault.service.ts @@ -37,7 +37,9 @@ export class VaultService { const config = this.prepareConfig(); config.headers['Content-Type'] = 'application/json'; return this.httpService - .get(`${this.vaultAddr}/v1/${mount}/data/${path}`, this.prepareConfig()) + .get<{ + data?: { [key: string]: any }; + }>(`${this.vaultAddr}/v1/${mount}/data/${path}`, this.prepareConfig()) .pipe( map((response) => { const kvData = response.data?.data?.data;