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

Arc 2643 audit log table #2559

Merged
merged 7 commits into from
Nov 22, 2023
Merged
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
2 changes: 2 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ GLOBAL_HASH_SECRET=testsecret
#Dyamno for deployment status
DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_REGION=us-west-1
DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_NAME=deployment-history-cache
DYNAMO_AUDIT_LOG_TABLE_REGION=us-west-1
DYNAMO_AUDIT_LOG_TABLE_NAME=audit-log

# The Postgres URL used to connect to the database and secret for encrypting data
DATABASE_URL=postgres://postgres:[email protected]:5432/jira-dev
Expand Down
2 changes: 2 additions & 0 deletions .env.e2e
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ DEBUG=nock.*
#Dyamno for deployment status
DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_REGION=us-west-1
DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_NAME=deployment-history-cache
DYNAMO_AUDIT_LOG_TABLE_REGION=us-west-1
DYNAMO_AUDIT_LOG_TABLE_NAME=audit-log

MICROS_AWS_REGION=us-west-1

Expand Down
2 changes: 2 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ DEBUG=nock.*
#Dyamno for deployment status
DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_REGION=us-west-1
DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_NAME=deployment-history-cache
DYNAMO_AUDIT_LOG_TABLE_REGION=us-west-1
DYNAMO_AUDIT_LOG_TABLE_NAME=audit-log

MICROS_AWS_REGION=us-west-1

Expand Down
21 changes: 20 additions & 1 deletion .localstack/dynamodb.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash

# DEPLOYMENT_HISTORY_CACHE_TABLE
echo "===== creating dynamo table ${DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_NAME} ====="

awslocal dynamodb create-table \
Expand All @@ -15,8 +15,27 @@ awslocal dynamodb create-table \
ReadCapacityUnits=10,WriteCapacityUnits=5

echo "===== table ${DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_NAME} created ====="

# AUDIT_LOG_TABLE
echo "===== creating dynamo table ${DYNAMO_AUDIT_LOG_TABLE_NAME} ====="

awslocal dynamodb create-table \
--table-name $DYNAMO_AUDIT_LOG_TABLE_NAME \
--key-schema \
AttributeName=Id,KeyType=HASH \
AttributeName=CreatedAt,KeyType=RANGE \
--attribute-definitions \
AttributeName=Id,AttributeType=S \
AttributeName=CreatedAt,AttributeType=N \
--region $DYNAMO_AUDIT_LOG_TABLE_REGION \
--provisioned-throughput \
ReadCapacityUnits=10,WriteCapacityUnits=5

echo "===== table ${DYNAMO_AUDIT_LOG_TABLE_NAME} created ====="

echo "===== checking now ====="

awslocal dynamodb list-tables --region $DYNAMO_AUDIT_LOG_TABLE_REGION
awslocal dynamodb list-tables --region $DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_REGION

echo "===== check finished ====="
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ services:
environment:
- DEFAULT_REGION=us-west-1
- DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_NAME=deployment-history-cache
- DYNAMO_AUDIT_LOG_TABLE_NAME=audit-log
- DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_REGION=us-west-1
- DYNAMO_AUDIT_LOG_TABLE_REGION=us-west-1
- LAMBDA_REMOTE_DOCKER=false
- LAMBDA_EXECUTOR=local # runs lambda inside temp directory instead of new docker container
- SQS_ENDPOINT_STRATEGY=off # sets the SQS queue domain/path to the legacy version
Expand Down
23 changes: 23 additions & 0 deletions github-for-jira.sd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,17 @@ resources:
TTLAttributeName: ExpiredAfter
dataType:
- UGC/PrimaryIdentifier # Sha of a commit that point to user's code
- name: audit-log
type: dynamo-db
attributes: &audit-log-table-attributes
HashKeyName: Id
HashKeyType: "S"
RangeKeyName: CreatedAt
RangeKeyType: "N"
ReadWriteCapacityMode: ON_DEMAND
TTLAttributeName: ExpiredAfter
dataType:
- UGC/PrimaryIdentifier # Sha of a commit that point to user's code

scaling:
instance: t2.small
Expand Down Expand Up @@ -432,6 +443,10 @@ environmentOverrides:
type: dynamo-db
attributes:
<<: *table-attributes
- name: audit-log
type: dynamo-db
attributes:
<<: *audit-log-table-attributes

alarms:
overrides:
Expand Down Expand Up @@ -535,6 +550,10 @@ environmentOverrides:
type: dynamo-db
attributes:
<<: *table-attributes
- name: audit-log
type: dynamo-db
attributes:
<<: *audit-log-table-attributes

- type: globaledge
name: proxy
Expand Down Expand Up @@ -636,6 +655,10 @@ environmentOverrides:
type: dynamo-db
attributes:
<<: *table-attributes
- name: audit-log
type: dynamo-db
attributes:
<<: *audit-log-table-attributes

- type: globaledge
name: proxy
Expand Down
2 changes: 2 additions & 0 deletions src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ export interface EnvVars {
//DyamoDB for deployment status history
DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_REGION: string;
DYNAMO_DEPLOYMENT_HISTORY_CACHE_TABLE_NAME: string;
DYNAMO_AUDIT_LOG_TABLE_NAME: string;
DYNAMO_AUDIT_LOG_TABLE_REGION: string;

// Micros Lifecycle Env Vars
SNS_NOTIFICATION_LIFECYCLE_QUEUE_URL?: string;
Expand Down
2 changes: 2 additions & 0 deletions src/routes/api/api-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { ApiReplyFailedEntitiesFromDataDepotPost } from "./api-replay-failed-ent
import { RepoSyncState } from "models/reposyncstate";
import { ApiResyncFailedTasksPost } from "./api-resync-failed-tasks";
import { GHESVerificationRouter } from "./ghes-app-verification/ghes-app-verification-router";
import { AuditLogApiRouter } from "./audit-log/audit-log-api-router";

export const ApiRouter = Router();

Expand Down Expand Up @@ -226,6 +227,7 @@ ApiRouter.post("/reset-failed-pending-deployment-cursor", ResetFailedAndPendingD
ApiRouter.post("/replay-rejected-entities-from-data-depot", ApiReplyFailedEntitiesFromDataDepotPost);
ApiRouter.post("/resync-failed-tasks",ApiResyncFailedTasksPost);
ApiRouter.use("/verify/githubapp/:gitHubAppId", GHESVerificationRouter);
ApiRouter.use("/audit-log", AuditLogApiRouter);

ApiRouter.use("/jira", ApiJiraRouter);
ApiRouter.use("/:installationId", param("installationId").isInt(), returnOnValidationError, ApiInstallationRouter);
15 changes: 15 additions & 0 deletions src/routes/api/audit-log/audit-log-api-router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Router } from "express";
import { ApiAuditLogGetBySubscriptionId } from "./audit-log-get-by-sub-id";
import { param, query } from "express-validator";
import { returnOnValidationError } from "../api-utils";

export const AuditLogApiRouter = Router({ mergeParams: true });

AuditLogApiRouter.get("/subscription/:subscriptionId",
param("subscriptionId").isInt(),
query("entityType").isString(),
query("entityId").isString(),
query("issueKey").isString(),
returnOnValidationError,
ApiAuditLogGetBySubscriptionId
);
62 changes: 62 additions & 0 deletions src/routes/api/audit-log/audit-log-get-by-sub-id.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import supertest from "supertest";
import { when } from "jest-when";
import { omit } from "lodash";
import { getFrontendApp } from "../../../app";
import { DatabaseStateCreator, CreatorResult } from "test/utils/database-state-creator";
import { findLog } from "services/audit-log-service";

jest.mock("services/audit-log-service");

describe("AuditLogApiGetBySubscriptionId", () => {

const makeApiCall = (subscriptionId: string | number, params: Record<string, unknown>) => {
return supertest(getFrontendApp())
.get(`/api/audit-log/subscription/${subscriptionId}`)
.query(params)
.set("X-Slauth-Mechanism", "test")
.send();
};

let db: CreatorResult;
let params;

beforeEach(async () => {
db = await new DatabaseStateCreator().forServer().create();
params = {
issueKey: "ABC-123",
entityType: "commit",
entityId: "abcd-efgh-ijkl"
};
});

describe.each(["issueKey", "entityType", "entityId"])("param validation", (paramKey) => {
it(`should return 422 on missing param ${paramKey}`, async () => {
await makeApiCall(db.subscription.id, omit(params, paramKey))
.expect(422);
});
});

it(`should return error on missing subscription`, async () => {
await makeApiCall(db.subscription.id + 1, params)
.expect(500);
});


it("should return audit log data successfully", async () => {

when(findLog).calledWith({
subscriptionId: db.subscription.id,
issueKey: "ABC-123",
entityType: "commit",
entityId: "abcd-efgh-ijkl"
}, expect.anything()).mockResolvedValue({
name: "hello"
} as any);

const result = await makeApiCall(db.subscription.id, params).expect(200);

expect(result.body).toEqual({
name: "hello"
});
});
});
27 changes: 27 additions & 0 deletions src/routes/api/audit-log/audit-log-get-by-sub-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Request, Response } from "express";
import { Subscription } from "models/subscription";
import { findLog, AuditInfoPK } from "services/audit-log-service";

export const ApiAuditLogGetBySubscriptionId = async (req: Request, res: Response): Promise<void> => {

const { subscriptionId } = req.params;
const { issueKey, entityType, entityId } = req.query;

const subscription = await Subscription.findByPk(subscriptionId);

if (subscription === null) {
throw new Error("Cannot find subscription by id " + subscriptionId);
}

const auditInfo: AuditInfoPK = {
subscriptionId: Number(subscriptionId),
issueKey: String(issueKey),
entityType: String(entityType),
entityId: String(entityId)
};

const result = await findLog(auditInfo, req.log);

res.status(200).json(result);

};
113 changes: 113 additions & 0 deletions src/services/audit-log-service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { getLogger } from "config/logger";
import { envVars } from "config/env";
import { dynamodb as ddb } from "config/dynamodb";
import { createHashWithoutSharedSecret } from "utils/encryption";
import { auditLog, findLog } from "./audit-log-service";

const logger = getLogger("test");
const ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;

describe("audit log service", () => {
describe("auditLog", () => {
kamaksheeAtl marked this conversation as resolved.
Show resolved Hide resolved
it("should successfully save DD api call audit info to dynamo db", async () => {
const createdAt = new Date();
const subscriptionId = 241412;
const entityId = "25e1008";
const entityAction = "pushed";
const entityType = "commit";
const source = "backfill";
const issueKey = "ARC-2605";
const ID = `subID_${subscriptionId}_typ_${entityType}_id_${entityId}_issKey_${issueKey}`;
await auditLog(
{
source,
entityType,
entityAction,
entityId,
subscriptionId,
issueKey,
createdAt
},
logger
);
const result = await ddb
.getItem({
TableName: envVars.DYNAMO_AUDIT_LOG_TABLE_NAME,
Key: {
Id: { S: createHashWithoutSharedSecret(ID) },
CreatedAt: { N: String(createdAt.getTime()) }
},
AttributesToGet: [
"Id",
"CreatedAt",
"ExpiredAfter",
"source",
"entityType",
"entityAction",
"entityId",
"subscriptionId",
"issueKey"
]
})
.promise();
expect(result.$response.error).toBeNull();
expect(result.Item).toEqual({
Id: { S: createHashWithoutSharedSecret(ID) },
CreatedAt: { N: String(createdAt.getTime()) },
ExpiredAfter: {
N: String(
Math.floor((createdAt.getTime() + ONE_DAY_IN_MILLISECONDS) / 1000)
)
},
source: { S: source },
entityAction: { S: entityAction },
entityId: { S: entityId },
entityType: { S: entityType },
issueKey: { S: issueKey },
subscriptionId: { N: String(subscriptionId) }
});
});

describe("auditLog", () => {
it("should successfully save DD api call audit info to dynamo db", async () => {
const createdAt = new Date();
const subscriptionId = 241412;
const entityId = "25e1008";
const entityAction = "pushed";
const entityType = "commit";
const source = "backfill";
const issueKey = "ARC-2605";
await auditLog(
{
source,
entityType,
entityAction,
entityId,
subscriptionId,
issueKey,
createdAt
},
logger
);
const result = await findLog(
{
entityType,
entityId,
subscriptionId,
issueKey
},
logger
);
expect(result).toEqual([{
entityAction,
entityId,
entityType,
issueKey,
source,
subscriptionId,
createdAt
}]);
});
});
});
});
Loading
Loading