Skip to content

Commit

Permalink
feat: add github sync audit
Browse files Browse the repository at this point in the history
  • Loading branch information
mbystedt committed Sep 13, 2024
1 parent 2bc9866 commit 484abd4
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 54 deletions.
60 changes: 59 additions & 1 deletion src/audit/audit.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down
68 changes: 61 additions & 7 deletions src/collection/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BadRequestException,
NotFoundException,
ServiceUnavailableException,
HttpException,
} from '@nestjs/common';
import { Request } from 'express';
import { Cron, CronExpression } from '@nestjs/schedule';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 =
Expand All @@ -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})`,
);
}
}

Expand Down
75 changes: 30 additions & 45 deletions src/github/github.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,18 @@ 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(
this.vaultService.getKv(VAULT_KV_APPS_MOUNT, path),
);
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());
}
}
}
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/vault/vault.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 484abd4

Please sign in to comment.