Skip to content

Commit

Permalink
[FE][CPF-39]: Scan frontend with Trivy (#47)
Browse files Browse the repository at this point in the history
* feat: api refactor, docker image push action

* feat: scan and upload to security

* chore: change owner name

* feat: adjust envs
  • Loading branch information
r1skz3ro authored Jul 2, 2024
1 parent edf1b34 commit 8facbef
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 66 deletions.
2 changes: 1 addition & 1 deletion .envexample
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ FUSIONAUTH_URL: http://fusionauth:9011
FUSIONAUTH_APPLICATION_ID: 23e4b229-1219-42e5-aed6-f9b6f1eedef8

# Frontend
API_BASE_URL=http://proxy/cpf/api
NEXT_PUBLIC_API_BASE_URL: http://proxy/cpf/api
55 changes: 55 additions & 0 deletions .github/workflows/trivy-frontend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Scan Frontend with Trivy

on:
workflow_dispatch:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
branches:
- main
- develop

env:
IMAGE_NAME: frontend
VERSION: v1

jobs:
build_docker_image:
name: Build docker image
timeout-minutes: 15
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Build docker image
run: docker build . --file Dockerfile --tag $IMAGE_NAME

- name: Log in to gHRC
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Push image
run: |
docker tag $IMAGE_NAME ghcr.io/tivix/cpf/$IMAGE_NAME:$VERSION
docker push ghcr.io/tivix/cpf/$IMAGE_NAME:$VERSION
- name: Run Trivy vulnerability scanner
uses: aquasecurity/[email protected]
with:
image-ref: "ghcr.io/tivix/cpf/${{ env.IMAGE_NAME }}:${{ env.VERSION }}"
scanners: "vuln,secret,config"
format: "sarif"
output: "trivy-fe-results.sarif"
severity: "CRITICAL,HIGH"

- name: Upload scan result to Github Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-fe-results.sarif
category: "image"
16 changes: 16 additions & 0 deletions frontend/src/api/bucket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { mapKeysToCamelCase } from '@app/utils';
import { API_URLS } from '.';
import { Bucket } from '@app/types/common';

async function getBucketDetails(slug: string) {
const response = await fetch(`${API_URLS.library.buckets}/${slug}`);

if (!response.ok) {
throw new Error('Failed to fetch bucket details');
}
const data = await response.json();

return mapKeysToCamelCase<Bucket>(data);
}

export { getBucketDetails };
2 changes: 1 addition & 1 deletion frontend/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const baseUrl = process.env.API_BASE_URL;
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL;

export const API_URLS = {
library: {
Expand Down
45 changes: 45 additions & 0 deletions frontend/src/api/ladder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { mapKeysToCamelCase } from '@app/utils';
import { API_URLS } from '.';
import { LadderCardInterface } from '@app/components/common/LadderCard';
import { LadderBand } from '@app/types/common';

async function getLadders() {
const response = await fetch(API_URLS.library.ladders);

if (!response.ok) {
throw new Error('Failed to fetch ladders');
}
const data = await response.json();

return mapKeysToCamelCase<LadderCardInterface[]>(data);
}

async function getLadderDetails(slug: string) {
const response = await fetch(`${API_URLS.library.ladders}/${slug}`);

if (!response.ok) {
throw new Error('Failed to fetch ladder details');
}
const data = await response.json();

return mapKeysToCamelCase<{
ladderName: string;
bands: Record<string, LadderBand>;
}>(data);
}

async function getLadderName(slug: string) {
const response = await fetch(`${API_URLS.library.ladders}/${slug}`);

if (!response.ok) {
throw new Error('Failed to fetch ladder details');
}
const data = await response.json();

return mapKeysToCamelCase<{
ladderName: string;
bands: Record<string, LadderBand>;
}>(data).ladderName;
}

export { getLadders, getLadderDetails, getLadderName };
32 changes: 4 additions & 28 deletions frontend/src/app/(app)/library/[ladder]/[bucket]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,9 @@
import { Breadcrumbs } from '@app/components/modules/Breadcrumbs';
import { mapKeysToCamelCase } from '@app/utils';
import { BucketDetails } from '@app/components/modules/BucketDetails';
import { API_URLS } from '@app/api';
import { Bucket, LadderBand } from '@app/types/common';
import { getLadderName } from '@app/api/ladder';
import { getBucketDetails } from '@app/api/bucket';
import { routes } from '@app/constants';

async function getBucketDetails(slug: string) {
const response = await fetch(`${API_URLS.library.buckets}/${slug}`);

if (!response.ok) {
throw new Error('Failed to fetch bucket details');
}
const data = await response.json();

return mapKeysToCamelCase<Bucket>(data);
}

async function getLadderName(slug: string) {
const response = await fetch(`${API_URLS.library.ladders}/${slug}`);

if (!response.ok) {
throw new Error('Failed to fetch ladder details');
}
const data = await response.json();

return mapKeysToCamelCase<{
ladderName: string;
bands: Record<string, LadderBand>;
}>(data).ladderName;
}

export default async function BucketDetailed({ params }: { params: { bucket: string; ladder: string } }) {
const { bucket, ladder } = params;
const data = await getBucketDetails(bucket);
Expand All @@ -48,3 +22,5 @@ export default async function BucketDetailed({ params }: { params: { bucket: str
</div>
);
}

export const dynamic = 'force-dynamic';
20 changes: 3 additions & 17 deletions frontend/src/app/(app)/library/[ladder]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
import { Breadcrumbs } from '@app/components/modules/Breadcrumbs';
import { LibraryDetailed } from '@app/components/modules/LibraryDetailed';
import { mapKeysToCamelCase } from '@app/utils';
import { API_URLS } from '@app/api';
import { LadderBand } from '@app/types/common';
import { getLadderDetails } from '@app/api/ladder';
import { routes } from '@app/constants';

async function getLadderDetails(slug: string) {
const response = await fetch(`${API_URLS.library.ladders}/${slug}`);

if (!response.ok) {
throw new Error('Failed to fetch ladder details');
}
const data = await response.json();

return mapKeysToCamelCase<{
ladderName: string;
bands: Record<string, LadderBand>;
}>(data);
}

export default async function LadderDetailed({ params }: { params: { ladder: string } }) {
const data = await getLadderDetails(params.ladder);

Expand All @@ -34,3 +18,5 @@ export default async function LadderDetailed({ params }: { params: { ladder: str
</div>
);
}

export const dynamic = 'force-dynamic';
20 changes: 5 additions & 15 deletions frontend/src/app/(app)/library/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
import { mapKeysToCamelCase } from '@app/utils';
import { LadderCard, LadderCardInterface } from '@app/components/common/LadderCard';
import { API_URLS } from '@app/api';

async function getLadders() {
const response = await fetch(API_URLS.library.ladders);

if (!response.ok) {
throw new Error('Failed to fetch ladders');
}
const data = await response.json();

return mapKeysToCamelCase<LadderCardInterface[]>(data);
}
import { LadderCard } from '@app/components/common/LadderCard';
import { getLadders } from '@app/api/ladder';

export default async function LibraryPage() {
const data = await getLadders();
Expand All @@ -21,10 +9,12 @@ export default async function LibraryPage() {
<h1 className="mb-10 text-lg font-semibold leading-6 text-navy-900">CPF Library</h1>
<p className="mb-6 tracking-wide text-navy-600">Select a career path to view the details.</p>
<div className="grid grid-cols-3 gap-6">
{data.map((ladder: LadderCardInterface) => (
{data.map((ladder) => (
<LadderCard key={ladder.ladderSlug} {...ladder} />
))}
</div>
</div>
);
}

export const dynamic = 'force-dynamic';
6 changes: 3 additions & 3 deletions frontend/src/static/icons/ChevronDoubleLeft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ export const ChevronDoubleLeft: React.FC<React.SVGProps<SVGSVGElement>> = (props
<path
d="M10 16L6.53034 12.5303C6.23745 12.2374 6.23745 11.7626 6.53034 11.4697L10 8M17 16L13.5303 12.5303C13.2374 12.2374 13.2374 11.7626 13.5303 11.4697L17 8"
stroke="#A1A5AC"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
2 changes: 1 addition & 1 deletion frontend/src/static/icons/CloseIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const CloseIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path d="M0.75 0.75L15.25 15.25M15.25 0.75L0.75 15.25" stroke="#555C6B" stroke-width="1.5" stroke-linecap="round" />
<path d="M0.75 0.75L15.25 15.25M15.25 0.75L0.75 15.25" stroke="#555C6B" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);

0 comments on commit 8facbef

Please sign in to comment.