Configuration File
Create.gitlab-ci.yml:
Copy
Ask AI
stages:
- build
- scan
variables:
# Replace {slug} with your organization's tenant identifier
BINARLY_AUTH_URL: https://auth-{slug}.binarly.cloud/realms/BinarlyRealm/protocol/openid-connect/token
BINARLY_API_URL: https://dashboard-{slug}.binarly.cloud
build:
stage: build
script:
- make firmware
artifacts:
paths:
- build/firmware.bin
binarly-scan:
stage: scan
image: curlimages/curl:latest
needs:
- build
before_script:
- apk add --no-cache jq
script:
# Authenticate
- |
TOKEN=$(curl -s --fail-with-body \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=${BINARLY_CLIENT_ID}" \
--data-urlencode "client_secret=${BINARLY_CLIENT_SECRET}" \
"$BINARLY_AUTH_URL" | jq -r '.access_token')
# Generate upload URL
- |
UPLOAD_RES=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$BINARLY_API_URL/api/v4/products/${BINARLY_PRODUCT_ID}/tempFiles:generateUploadUrl")
UPLOAD_URL=$(echo $UPLOAD_RES | jq -r '.uploadUrl')
TEMP_ID=$(echo $UPLOAD_RES | jq -r '.id')
# Upload binary
- curl -s -X PUT --data-binary @./build/firmware.bin "$UPLOAD_URL"
# Finalize upload
- |
IMAGE_RES=$(curl -s -H "Authorization: Bearer $TOKEN" \
-F "tempFileId=$TEMP_ID" \
-F "imageName=GitLab Pipeline $CI_PIPELINE_ID" \
-F "version=$CI_COMMIT_SHORT_SHA" \
-F "file=@./build/firmware.bin" \
"$BINARLY_API_URL/api/v4/products/${BINARLY_PRODUCT_ID}/images:upload")
IMAGE_ID=$(echo $IMAGE_RES | jq -r '.id')
echo "Image ID: $IMAGE_ID"
# Poll for completion
- |
STATUS="running"
while [ "$STATUS" != "completed" ]; do
sleep 30
SCAN_RES=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$BINARLY_API_URL/api/v4/products/${BINARLY_PRODUCT_ID}/images/$IMAGE_ID/scans?status=true")
STATUS=$(echo $SCAN_RES | jq -r '.scans[0].latestScanState.type')
echo "Scan status: $STATUS"
[ "$STATUS" == "failed" ] && exit 1
done
echo "Scan completed successfully"
# Verify Results
- |
echo "Binarly Security Verification"
# Configuration
FAIL_ON_STATUS="${FAIL_ON_STATUS:-new,inProgress}"
echo "Failing on statuses: $FAIL_ON_STATUS"
# Get all images
IMAGES_RES=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$BINARLY_API_URL/api/v4/products/${BINARLY_PRODUCT_ID}/images")
IMAGE_COUNT=$(echo "$IMAGES_RES" | jq '.images | length')
echo "Product has ${IMAGE_COUNT} image(s)"
# Compare if multiple images exist
if [ "$IMAGE_COUNT" -gt 1 ]; then
# Sort images by createTime descending
LATEST_ID=$(echo "$IMAGES_RES" | jq -r '[.images | sort_by(.createTime) | reverse][0][0].id')
PREVIOUS_ID=$(echo "$IMAGES_RES" | jq -r '[.images | sort_by(.createTime) | reverse][0][1].id')
echo "Comparing: $PREVIOUS_ID → $LATEST_ID"
RESOLVED=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"filters":[{"field":"compareLeftImageId","value":"'"$PREVIOUS_ID"'","comparator":"equals"},{"field":"compareRightImageId","value":"'"$LATEST_ID"'","comparator":"equals"},{"field":"compareSide","value":"left","comparator":"equals"}]}' \
"$BINARLY_API_URL/api/v4/grids/findings:gridList" | jq '.total // 0')
UNCHANGED=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"filters":[{"field":"compareLeftImageId","value":"'"$PREVIOUS_ID"'","comparator":"equals"},{"field":"compareRightImageId","value":"'"$LATEST_ID"'","comparator":"equals"},{"field":"compareSide","value":"both","comparator":"equals"}]}' \
"$BINARLY_API_URL/api/v4/grids/findings:gridList" | jq '.total // 0')
NEW_ISSUES=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"filters":[{"field":"compareLeftImageId","value":"'"$PREVIOUS_ID"'","comparator":"equals"},{"field":"compareRightImageId","value":"'"$LATEST_ID"'","comparator":"equals"},{"field":"compareSide","value":"right","comparator":"equals"}]}' \
"$BINARLY_API_URL/api/v4/grids/findings:gridList" | jq '.total // 0')
echo "Resolved: $RESOLVED | Unchanged: $UNCHANGED | New: $NEW_ISSUES"
fi
# Check for findings matching configured statuses (using 'in' comparator with array)
STATUSES_JSON=$(echo "$FAIL_ON_STATUS" | tr ',' '\n' | jq -R . | jq -s .)
# Use LATEST_ID if available (from comparison), otherwise use IMAGE_ID from upload step
CHECK_IMAGE_ID="${LATEST_ID:-$IMAGE_ID}"
TOTAL_ACTIONABLE=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"filters":[{"field":"imageId","value":"'"$CHECK_IMAGE_ID"'","comparator":"equals"},{"field":"issueStatus","value":'"$STATUSES_JSON"',"comparator":"in"}]}' \
"$BINARLY_API_URL/api/v4/grids/findings:gridList" | jq '.total // 0')
echo " Statuses checked: $FAIL_ON_STATUS"
echo "Total actionable findings: $TOTAL_ACTIONABLE"
if [ "$TOTAL_ACTIONABLE" -gt 0 ]; then
echo "BUILD FAILED: $TOTAL_ACTIONABLE actionable finding(s)"
exit 1
fi
echo "BUILD PASSED: No actionable findings"
Required Variables
Configure in Settings → CI/CD → Variables:| Variable | Flags | Description |
|---|---|---|
BINARLY_CLIENT_ID | Protected, Masked | Keycloak API Client ID |
BINARLY_CLIENT_SECRET | Protected, Masked | Keycloak API Client Secret |
BINARLY_PRODUCT_ID | Protected | Target product ID |
GitLab-Specific Variables
The script uses these built-in GitLab variables:$CI_PIPELINE_ID– Unique pipeline identifier$CI_COMMIT_SHORT_SHA– Short commit hash for version tracking