Skip to content
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
52 changes: 40 additions & 12 deletions fogwall-dashboard/frontend/src/pages/PushDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import { ColorSchemeType } from 'diff2html/lib/types'
import 'diff2html/bundles/css/diff2html.min.css'
import { approvePush, cancelPush, fetchDiff, fetchProviders, fetchPush, rejectPush } from '../api'
import { StatusBadge } from '../components/StatusBadge'
import type { AttestationQuestion, CurrentUser, Provider, PushRecord, Step } from '../types'
import type {
AttestationLink,
AttestationQuestion,
CurrentUser,
Provider,
PushRecord,
Step,
} from '../types'

// Steps that are infrastructure/pre-processing, not user-visible validation checks
const NON_VALIDATION_STEPS = new Set([
Expand Down Expand Up @@ -271,18 +278,37 @@ function AttestationQuestionField({
</span>
)

const linksEl = question.links && question.links.length > 0 && (
<div className="flex flex-wrap gap-x-3 gap-y-0.5 mt-0.5 ml-6">
{question.links.map((link: AttestationLink) => (
<a
key={link.url}
href={link.url}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-blue-500 hover:underline dark:text-blue-400"
>
{link.text} ↗
</a>
))}
</div>
)

if (question.type === 'checkbox') {
return (
<label className="flex items-start gap-2 cursor-pointer">
<input
type="checkbox"
checked={value === 'true'}
disabled={disabled}
onChange={(e) => onChange(e.target.checked ? 'true' : 'false')}
className="mt-0.5 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 disabled:opacity-50 dark:border-slate-600"
/>
{labelEl}
</label>
<div>
<label className="flex items-start gap-2 cursor-pointer">
<input
type="checkbox"
checked={value === 'true'}
disabled={disabled}
onChange={(e) => onChange(e.target.checked ? 'true' : 'false')}
className="mt-0.5 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 disabled:opacity-50 dark:border-slate-600"
/>
{labelEl}
</label>
{linksEl}
</div>
)
}

Expand All @@ -303,6 +329,7 @@ function AttestationQuestionField({
</option>
))}
</select>
{linksEl}
</div>
)
}
Expand All @@ -315,9 +342,10 @@ function AttestationQuestionField({
value={value}
disabled={disabled}
onChange={(e) => onChange(e.target.value)}
placeholder={question.tooltip ?? ''}
placeholder=""
className="border border-gray-300 rounded px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-300 disabled:bg-gray-50 disabled:text-gray-400 dark:bg-slate-700 dark:border-slate-600 dark:text-gray-200 dark:disabled:bg-gray-800 dark:disabled:text-gray-500"
/>
{linksEl}
</div>
)
}
Expand Down
7 changes: 6 additions & 1 deletion fogwall-dashboard/frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@ export interface Commit {
signedOffBy?: string[]
}

export interface AttestationLink {
text: string
url: string
}

export interface AttestationQuestion {
id: string
type: 'checkbox' | 'text' | 'dropdown'
label: string
required: boolean
options?: string[]
tooltip?: string
links?: AttestationLink[]
}

export interface Attestation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ public class AttestationQuestion {
/** Options for {@code dropdown} type questions. Ignored for other types. */
private List<String> options = new ArrayList<>();

/** Optional tooltip / help text shown alongside the question. */
private String tooltip;
/** Optional policy or reference links rendered below the question label. */
private List<AttestationLink> links = new ArrayList<>();

/** A labelled URL rendered as an anchor below an attestation question. */
@Data
public static class AttestationLink {
private String text;
private String url;
}
}
46 changes: 46 additions & 0 deletions test/gitea-push-fail.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Failure-path store-and-forward push to local Gitea via fogwall.
# Verifies secret scanning correctly rejects a commit containing an AWS key.
# Requires: docker compose stack up + docker/gitea-setup.sh already run.
set -euo pipefail

source "$(dirname "${BASH_SOURCE[0]}")/env.sh"
source "$(dirname "${BASH_SOURCE[0]}")/gitea/tokens.env"

GITEA_HOST="${GITEA_HOST:-localhost:3000}"
GIT_USERNAME="${GIT_USERNAME:-me}"
GIT_PASSWORD="${GITEA_TESTUSER_TOKEN}"
PUSH_URL="http://${GIT_USERNAME}:${GIT_PASSWORD}@localhost:8080/push/${GITEA_HOST}/test-owner/test-repo.git"
TEST_BRANCH="test/gitea-fail-$(date +%s)"
REPO_DIR=$(mktemp -d "${_SYS_TMPDIR}/gitea-push-fail-XXXX")

cleanup() {
git -C "${REPO_DIR}" remote set-url origin "http://${GIT_USERNAME}:${GIT_PASSWORD}@${GITEA_HOST}/test-owner/test-repo.git" 2>/dev/null || true
git -C "${REPO_DIR}" push origin --delete "${TEST_BRANCH}" 2>/dev/null || true
safe_rm_rf "${REPO_DIR}"
}
trap cleanup EXIT

git clone "${PUSH_URL}" "${REPO_DIR}"
cd "${REPO_DIR}"
git checkout -b "${TEST_BRANCH}"
git config user.name "test-user"
git config user.email "testuser@example.com"

cat > aws-credentials << 'EOF'
[default]
aws_access_key_id = AKIAYVP4CIPPH3TESTKEY
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
EOF
git add aws-credentials
git commit -m "chore: add deployment credentials"

push_exit=0
git push origin "${TEST_BRANCH}" 2>&1 || push_exit=$?

if [[ ${push_exit} -ne 0 ]]; then
echo "PASSED (push correctly rejected by secret scanning)"
else
echo "FAILED (push should have been rejected)"
exit 1
fi
34 changes: 34 additions & 0 deletions test/gitea-push-pass.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Golden-path store-and-forward push to local Gitea via fogwall.
# Requires: docker compose stack up + docker/gitea-setup.sh already run.
set -euo pipefail

source "$(dirname "${BASH_SOURCE[0]}")/env.sh"
source "$(dirname "${BASH_SOURCE[0]}")/gitea/tokens.env"

GITEA_HOST="${GITEA_HOST:-localhost:3000}"
GIT_USERNAME="${GIT_USERNAME:-me}"
GIT_PASSWORD="${GITEA_TESTUSER_TOKEN}"
PUSH_URL="http://${GIT_USERNAME}:${GIT_PASSWORD}@localhost:8080/push/${GITEA_HOST}/test-owner/test-repo.git"
TEST_BRANCH="test/gitea-pass-$(date +%s)"
REPO_DIR=$(mktemp -d "${_SYS_TMPDIR}/gitea-push-pass-XXXX")

cleanup() {
git -C "${REPO_DIR}" remote set-url origin "http://${GIT_USERNAME}:${GIT_PASSWORD}@${GITEA_HOST}/test-owner/test-repo.git" 2>/dev/null || true
git -C "${REPO_DIR}" push origin --delete "${TEST_BRANCH}" 2>/dev/null || true
safe_rm_rf "${REPO_DIR}"
}
trap cleanup EXIT

git clone "${PUSH_URL}" "${REPO_DIR}"
cd "${REPO_DIR}"
git checkout -b "${TEST_BRANCH}"
git config user.name "test-user"
git config user.email "testuser@example.com"

echo "pass - $(date)" >> test-file.txt
git add test-file.txt
git commit -m "feat: golden-path gitea store-and-forward test"
git push origin "${TEST_BRANCH}"

echo "PASSED"
Loading