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

Integration test for secret ops #1408

Merged
merged 20 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f09c48d
feat(e2e): fixed seed file issue and added more seeding
akhilmhdh Feb 15, 2024
00876f7
feat(e2e): added secrets integration tests
akhilmhdh Feb 15, 2024
90f09c7
feat(e2e): added service token integration tests
akhilmhdh Feb 15, 2024
4c49119
feat(e2e): added identity token secret access integration tests
akhilmhdh Feb 15, 2024
b24d748
feat(e2e): added gh action for execution of integration tests on PR a…
akhilmhdh Feb 15, 2024
793440f
feat(e2e): made rebased changes
akhilmhdh Feb 15, 2024
c71af00
feat(e2e): added enc key on gh action for be test file
akhilmhdh Feb 15, 2024
4f3cf04
feat(e2e): fixed post clean up error on gh be integration action
akhilmhdh Feb 15, 2024
b84579b
feat(e2e): changed to root .env.test and added .env.test.example
akhilmhdh Feb 15, 2024
c6c64b5
trigger workflow
maidul98 Feb 17, 2024
abd28d9
remove comment
maidul98 Feb 17, 2024
8864c81
feat: updated to reuse and run integration api test before release
akhilmhdh Feb 18, 2024
678306b
feat: some name changes for better understanding on testing
akhilmhdh Feb 18, 2024
7070a69
feat: made e2ee api test indepdent or stateless
akhilmhdh Feb 19, 2024
35d589a
add more secret test cases
maidul98 Feb 19, 2024
e4b8937
add env slug to make expect more strict
maidul98 Feb 19, 2024
d428fd0
update test envs
maidul98 Feb 19, 2024
2996efe
feat: made secrets ops test to have both identity and jwt token based…
akhilmhdh Feb 20, 2024
b4db06c
return empty string for decrypt fuction
maidul98 Feb 20, 2024
ccaa9fd
add more test cases
maidul98 Feb 20, 2024
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
7 changes: 7 additions & 0 deletions .env.test.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DB_CONNECTION_URI=postgres://infisical:infisical@localhost:5430/infisical-test
akhilmhdh marked this conversation as resolved.
Show resolved Hide resolved
REDIS_URL=redis://redis:6379
# Keys
# Required key for platform encryption/decryption ops
# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NOT BE USED FOR PRODUCTION
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
AUTH_SECRET=something-random
48 changes: 48 additions & 0 deletions .github/workflows/run-backend-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: "Run backend tests"

on:
pull_request:
types: [opened, synchronize]
paths:
- "backend/**"
- "!backend/README.md"
- "!backend/.*"
- "backend/.eslintrc.js"
push:
tags:
- "infisical/v*.*.*-postgres"
maidul98 marked this conversation as resolved.
Show resolved Hide resolved
jobs:
check-be-pr:
name: Run integration test
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- uses: KengoTODA/actions-setup-docker-compose@v1
if: ${{ env.ACT }}
maidul98 marked this conversation as resolved.
Show resolved Hide resolved
name: Install `docker-compose` for local simulations
with:
version: "2.14.2"
- name: 🔧 Setup Node 20
uses: actions/setup-node@v3
with:
node-version: "20"
cache: "npm"
cache-dependency-path: backend/package-lock.json
- name: Install dependencies
run: npm install
working-directory: backend
- name: Start postgres and redis
run: touch .env && docker-compose -f docker-compose.dev.yml up -d db redis
maidul98 marked this conversation as resolved.
Show resolved Hide resolved
- name: Start integration test
run: npm run test:e2e
working-directory: backend
env:
REDIS_URL: redis://172.17.0.1:6379
DB_CONNECTION_URI: postgres://infisical:infisical@172.17.0.1:5432/infisical?sslmode=disable
AUTH_SECRET: something-random
ENCRYPTION_KEY: 4bnfe4e407b8921c104518903515b218
- name: cleanup
run: |
docker-compose -f "docker-compose.dev.yml" down
akhilmhdh marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions backend/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
vitest-environment-infisical.ts
vitest.config.ts
vitest.e2e.config.ts
12 changes: 12 additions & 0 deletions backend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ module.exports = {
tsconfigRootDir: __dirname
},
root: true,
overrides: [
{
files: ["./e2e-test/**/*"],
rules: {
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-call": "off",
}
}
],
rules: {
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-unsafe-enum-comparison": "off",
Expand Down
281 changes: 281 additions & 0 deletions backend/e2e-test/routes/v1/identity.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
import { SecretType } from "@app/db/schemas";
import { getUserPrivateKey, seedData1 } from "@app/db/seed-data";
import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";

describe("Identity token secret ops", async () => {
let identityToken = "";
beforeAll(async () => {
// enable bot
const res = await testServer.inject({
method: "GET",
url: `/api/v2/workspace/${seedData1.project.id}/encrypted-key`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
}
});
expect(res.statusCode).toEqual(200);
const projectKeyEnc = JSON.parse(res.payload);
maidul98 marked this conversation as resolved.
Show resolved Hide resolved

const userInfoRes = await testServer.inject({
method: "GET",
url: "/api/v2/users/me",
headers: {
authorization: `Bearer ${jwtAuthToken}`
}
});
expect(userInfoRes.statusCode).toEqual(200);
const { user: userInfo } = JSON.parse(userInfoRes.payload);

const privateKey = await getUserPrivateKey(seedData1.password, userInfo);
const projectKey = decryptAsymmetric({
ciphertext: projectKeyEnc.encryptedKey,
nonce: projectKeyEnc.nonce,
publicKey: projectKeyEnc.sender.publicKey,
privateKey
});

const projectBotRes = await testServer.inject({
method: "GET",
url: `/api/v1/bot/${seedData1.project.id}`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
}
});
expect(projectBotRes.statusCode).toEqual(200);
const projectBot = JSON.parse(projectBotRes.payload).bot;
const botKey = encryptAsymmetric(projectKey, projectBot.publicKey, privateKey);

// set bot as active
const setBotActive = await testServer.inject({
method: "PATCH",
url: `/api/v1/bot/${projectBot.id}/active`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
},
body: {
isActive: true,
workspaceId: seedData1.project.id,
botKey: {
encryptedKey: botKey.ciphertext,
nonce: botKey.nonce
}
}
});
expect(setBotActive.statusCode).toEqual(200);

// set identity token
const identityLogin = await testServer.inject({
method: "POST",
url: "/api/v1/auth/universal-auth/login",
body: {
clientSecret: seedData1.machineIdentity.clientCred.secret,
clientId: seedData1.machineIdentity.clientCred.id
}
});
expect(identityLogin.statusCode).toBe(200);
identityToken = identityLogin.json().accessToken;

// create a deep folder
const folderCreate = await testServer.inject({
method: "POST",
url: `/api/v1/folders`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
},
body: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
name: "folder",
path: "/nested1/nested2"
}
});
expect(folderCreate.statusCode).toBe(200);
});

afterAll(async () => {
const projectBotRes = await testServer.inject({
method: "GET",
url: `/api/v1/bot/${seedData1.project.id}`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
}
});
expect(projectBotRes.statusCode).toEqual(200);
const projectBot = JSON.parse(projectBotRes.payload).bot;

// set bot as inactive
const setBotInActive = await testServer.inject({
method: "PATCH",
url: `/api/v1/bot/${projectBot.id}/active`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
},
body: {
isActive: false,
workspaceId: seedData1.project.id
}
});
expect(setBotInActive.statusCode).toEqual(200);
});

const testRawSecrets = [
{
path: "/",
secret: {
key: "ID-SEC",
value: "something-secret",
comment: "some comment"
}
},
{
path: "/nested1/nested2/folder",
secret: {
key: "NESTED-ID-SEC",
value: "something-secret",
comment: "some comment"
}
}
];

const getSecrets = async (environment: string, secretPath = "/") => {
const res = await testServer.inject({
method: "GET",
url: `/api/v3/secrets/raw`,
headers: {
authorization: `Bearer ${identityToken}`
},
query: {
secretPath,
environment,
workspaceId: seedData1.project.id
}
});
const secrets: { secretKey: string; secretValue: string; type: SecretType; version: number }[] =
JSON.parse(res.payload).secrets || [];
return secrets.map((el) => ({ key: el.secretKey, value: el.secretValue, type: el.type, version: el.version }));
};

test.each(testRawSecrets)("Create secret raw in path $path", async ({ secret, path }) => {
const createSecretReqBody = {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
type: SecretType.Shared,
secretValue: secret.value,
secretComment: secret.comment,
secretPath: path
};
const createSecRes = await testServer.inject({
method: "POST",
url: `/api/v3/secrets/raw/${secret.key}`,
headers: {
authorization: `Bearer ${identityToken}`
},
body: createSecretReqBody
});
expect(createSecRes.statusCode).toBe(200);
const createdSecretPayload = JSON.parse(createSecRes.payload);
expect(createdSecretPayload).toHaveProperty("secret");

// fetch secrets
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual(
expect.arrayContaining([
expect.objectContaining({
key: secret.key,
value: secret.value,
type: SecretType.Shared
})
])
);
});

test.each(testRawSecrets)("Get secret by name raw in path $path", async ({ secret, path }) => {
const getSecByNameRes = await testServer.inject({
method: "GET",
url: `/api/v3/secrets/raw/${secret.key}`,
headers: {
authorization: `Bearer ${identityToken}`
},
query: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
secretPath: path
}
});
expect(getSecByNameRes.statusCode).toBe(200);
const secretPayload = JSON.parse(getSecByNameRes.payload);
expect(secretPayload).toHaveProperty("secret");
expect(secretPayload.secret).toEqual(
expect.objectContaining({
secretKey: secret.key,
secretValue: secret.value
})
);
});

test.each(testRawSecrets)("Update secret raw in path $path", async ({ secret, path }) => {
const updateSecretReqBody = {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
type: SecretType.Shared,
secretValue: "new-value",
secretPath: path
};

const updateSecRes = await testServer.inject({
method: "PATCH",
url: `/api/v3/secrets/raw/${secret.key}`,
headers: {
authorization: `Bearer ${identityToken}`
},
body: updateSecretReqBody
});
expect(updateSecRes.statusCode).toBe(200);
const updatedSecretPayload = JSON.parse(updateSecRes.payload);
expect(updatedSecretPayload).toHaveProperty("secret");

// fetch secrets
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual(
expect.arrayContaining([
expect.objectContaining({
key: secret.key,
value: "new-value",
version: 2,
type: SecretType.Shared
})
])
);
});

test.each(testRawSecrets)("Delete secret raw in path $path", async ({ path, secret }) => {
const deletedSecretReqBody = {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
type: SecretType.Shared,
secretPath: path
};
const deletedSecRes = await testServer.inject({
method: "DELETE",
url: `/api/v3/secrets/raw/${secret.key}`,
headers: {
authorization: `Bearer ${identityToken}`
},
body: deletedSecretReqBody
});
expect(deletedSecRes.statusCode).toBe(200);
const deletedSecretPayload = JSON.parse(deletedSecRes.payload);
expect(deletedSecretPayload).toHaveProperty("secret");

// fetch secrets
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual(
expect.arrayContaining([
expect.not.objectContaining({
key: secret.key,
type: SecretType.Shared
})
])
);
});
});
3 changes: 2 additions & 1 deletion backend/e2e-test/routes/v1/login.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { seedData1 } from "@app/db/seed-data";
import jsrp from "jsrp";

import { seedData1 } from "@app/db/seed-data";

describe("Login V1 Router", async () => {
// eslint-disable-next-line
maidul98 marked this conversation as resolved.
Show resolved Hide resolved
const client = new jsrp.client();
Expand Down
6 changes: 3 additions & 3 deletions backend/e2e-test/routes/v1/secret-folder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ describe("Secret Folder Router", async () => {
expect(res.statusCode).toBe(200);
const payload = JSON.parse(res.payload);
expect(payload).toHaveProperty("folders");
expect(payload.folders.length).toBe(expected.length);
expect(payload).toEqual({ folders: expected.folders.map((el) => expect.objectContaining(el)) });
expect(payload).toEqual({
folders: expect.arrayContaining(expected.folders.map((el) => expect.objectContaining(el)))
});
});

let toBeDeleteFolderId = "";
Expand Down Expand Up @@ -106,7 +107,6 @@ describe("Secret Folder Router", async () => {
expect(resUpdatedFolders.statusCode).toBe(200);
const updatedFolderList = JSON.parse(resUpdatedFolders.payload);
expect(updatedFolderList).toHaveProperty("folders");
expect(updatedFolderList.folders.length).toEqual(1);
expect(updatedFolderList.folders[0].name).toEqual("folder-updated");
});

Expand Down
Loading
Loading