diff --git a/.github/workflows/hub_pipeline.yml b/.github/workflows/hub_pipeline.yml deleted file mode 100644 index 05bd8a9..0000000 --- a/.github/workflows/hub_pipeline.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: Hub Release & Publish - -env: - WORKING_DIR: ./Hub - -on: - push: - branches: [ "main" ] - tags: [ "v*.*.*" ] - paths: [ 'Hub/**' ] - workflow_dispatch: - -jobs: - code-security: - name: "🛡️ Security & SAST Scans" - runs-on: ubuntu-latest - permissions: - contents: read - security-events: write # REQUIRED FOR CODEQL TO UPLOAD ALERTS - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.25' - cache-dependency-path: ${{ env.WORKING_DIR }}/go.sum - - # --- NEW: Build Vue Frontend --- - # Go requires the ui/dist folder to exist for the go:embed directive to compile - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: ${{ env.WORKING_DIR }}/ui/package-lock.json - - - name: Build Vue Frontend - working-directory: ${{ env.WORKING_DIR }}/ui - run: | - npm ci - npm run build - # ------------------------------- - - # TODO: Uncomment this when we write the Fuzz tests! - # - name: Run Go Tests - # working-directory: ${{ env.WORKING_DIR }} - # run: go test -v ./... - - # ---------------------------------------------------- - # CODEQL SAST SCAN - # ---------------------------------------------------- - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: 'go' - - - name: Build for CodeQL - working-directory: ${{ env.WORKING_DIR }} - run: go build -v ./... - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:go/hub" - - # ---------------------------------------------------- - # TRIVY FILESYSTEM SCA SCAN - # ---------------------------------------------------- - - name: Run Trivy Vulnerability Scanner (FS) - uses: aquasecurity/trivy-action@master - with: - scan-type: 'fs' - scan-ref: ${{ env.WORKING_DIR }} - format: 'table' - exit-code: '1' - severity: 'CRITICAL,HIGH' - - publish-hub: - name: "🚀 Build & Publish Latest" - needs: [code-security] - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - id-token: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Enables Raspberry Pi Support - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Automatically just tags everything pushed to main as "latest" - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository_owner }}/honeywire-hub - tags: | - type=raw,value=latest,enable={{is_default_branch}} - type=semver,pattern={{version}} - - - name: Build and Push via Buildx - uses: docker/build-push-action@v5 - with: - context: ${{ env.WORKING_DIR }} - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - provenance: true - sbom: true - - - name: Summary - run: | - echo "### ✅ Hub Published Successfully" >> $GITHUB_STEP_SUMMARY - echo "Platforms: \`linux/amd64\`, \`linux/arm64\`" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/publish-hub.yml b/.github/workflows/publish-hub.yml new file mode 100644 index 0000000..3ba2c62 --- /dev/null +++ b/.github/workflows/publish-hub.yml @@ -0,0 +1,44 @@ +name: Publish Hub + +on: + push: + tags: + - 'hub/v*.*.*' + +permissions: + contents: write + packages: write + +jobs: + publish: + name: Build and Push Hub Image + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Extract Version + id: vars + run: | + TAG_REF="${GITHUB_REF#refs/tags/}" + VERSION=$(echo "$TAG_REF" | grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+') + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Build and Push Hub Docker Image + run: | + VERSION="${{ steps.vars.outputs.VERSION }}" + IMAGE_TAG="ghcr.io/${{ github.repository_owner }}/honeywire-hub:v$VERSION" + LATEST_TAG="ghcr.io/${{ github.repository_owner }}/honeywire-hub:latest" + + docker build -t "$IMAGE_TAG" -t "$LATEST_TAG" -f Hub/Dockerfile . + docker push "$IMAGE_TAG" + docker push "$LATEST_TAG" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: "Hub v${{ steps.vars.outputs.VERSION }}" + generate_release_notes: true diff --git a/.github/workflows/publish-sensor-registry.yml b/.github/workflows/publish-sensor-registry.yml new file mode 100644 index 0000000..fed1e38 --- /dev/null +++ b/.github/workflows/publish-sensor-registry.yml @@ -0,0 +1,134 @@ +name: Publish Sensor to Registry + +on: + push: + tags: + - 'sensor/**/v*.*.*' + +permissions: + contents: write + packages: write + +jobs: + publish: + name: "📦 Publish Sensor Manifest" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract sensor name and version from tag + id: vars + run: | + TAG_REF="${GITHUB_REF#refs/tags/}" + SENSOR_NAME=$(echo "$TAG_REF" | sed 's|^sensor/||' | sed 's|/v[0-9].*$||') + VERSION=$(echo "$TAG_REF" | grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+') + + if [ -z "$SENSOR_NAME" ] || [ -z "$VERSION" ]; then + exit 1 + fi + + echo "SENSOR_NAME=$SENSOR_NAME" >> $GITHUB_OUTPUT + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + + - name: Validate source manifest + id: validate + run: | + EXPECTED_ID="hw-sensor-${{ steps.vars.outputs.SENSOR_NAME }}" + SOURCE=$(find Sensors/official -name "*.json" | while read file; do + ID=$(jq -r '.id // empty' "$file" 2>/dev/null || true) + if [ "$ID" = "$EXPECTED_ID" ]; then + echo "$file" + exit 0 + fi + done) + + if [ -z "$SOURCE" ]; then + exit 1 + fi + echo "SOURCE=$SOURCE" >> $GITHUB_OUTPUT + + - name: Build versioned manifest + id: build + run: | + SOURCE="${{ steps.validate.outputs.SOURCE }}" + SENSOR_NAME="${{ steps.vars.outputs.SENSOR_NAME }}" + VERSION="${{ steps.vars.outputs.VERSION }}" + OUTPUT_FILE="${SENSOR_NAME}-v${VERSION}.json" + + jq --arg ver "$VERSION" --arg tag "v$VERSION" \ + '.version = $ver | + .deployment.image_tag = $tag | + if .deployment.init_containers then + .deployment.init_containers = [ + .deployment.init_containers[] | .image_tag = $tag + ] + else . end' \ + "$SOURCE" > "/tmp/$OUTPUT_FILE" + + echo "OUTPUT_FILE=$OUTPUT_FILE" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Build and Push Docker Image + run: | + SOURCE="${{ steps.validate.outputs.SOURCE }}" + SENSOR_DIR=$(dirname "$SOURCE") + VERSION="${{ steps.vars.outputs.VERSION }}" + + IMAGE_REPO=$(jq -r '.deployment.image_repository // empty' "$SOURCE") + if [ -z "$IMAGE_REPO" ]; then + exit 0 + fi + + IMAGE_TAG="$IMAGE_REPO:v$VERSION" + docker build -t "$IMAGE_TAG" -f "$SENSOR_DIR/Dockerfile" . + docker push "$IMAGE_TAG" | tee push_out.log + + DIGEST=$(grep -o 'sha256:[a-f0-9]\{64\}' push_out.log | tail -n 1) + if [ -n "$DIGEST" ]; then + OUTPUT_FILE="${{ steps.build.outputs.OUTPUT_FILE }}" + jq --arg digest "$DIGEST" '.deployment.image_digest = $digest | if .deployment.init_containers then .deployment.init_containers[].image_digest = $digest else . end' "/tmp/$OUTPUT_FILE" > "/tmp/$OUTPUT_FILE.tmp" && mv "/tmp/$OUTPUT_FILE.tmp" "/tmp/$OUTPUT_FILE" + else + exit 1 + fi + + - name: Checkout registry-pages branch + run: | + cp scripts/build-registry-index.sh /tmp/build-registry-index.sh + chmod +x /tmp/build-registry-index.sh + + git config user.name "HoneyWire CI" + git config user.email "ci@honeywire.dev" + + if git ls-remote --exit-code --heads origin registry-pages >/dev/null 2>&1; then + git fetch origin registry-pages + git checkout registry-pages + else + git checkout --orphan registry-pages + git rm -rf . 2>/dev/null || true + echo "# HoneyWire Sensor Registry" > README.md + git add README.md + git commit -m "Initialize registry-pages branch" + fi + + - name: Write versioned manifest to registry + run: | + OUTPUT_FILE="${{ steps.build.outputs.OUTPUT_FILE }}" + cp "/tmp/$OUTPUT_FILE" "./$OUTPUT_FILE" + + - name: Rebuild index.json + run: /tmp/build-registry-index.sh + + - name: Commit and push to registry-pages + run: | + SENSOR_NAME="${{ steps.vars.outputs.SENSOR_NAME }}" + VERSION="${{ steps.vars.outputs.VERSION }}" + OUTPUT_FILE="${{ steps.build.outputs.OUTPUT_FILE }}" + + git add "$OUTPUT_FILE" index.json + git commit -m "registry: publish ${SENSOR_NAME} v${VERSION}" + git push origin registry-pages diff --git a/.github/workflows/publish-wizard.yml b/.github/workflows/publish-wizard.yml new file mode 100644 index 0000000..1668fce --- /dev/null +++ b/.github/workflows/publish-wizard.yml @@ -0,0 +1,51 @@ +name: Publish Wizard + +on: + push: + tags: + - 'wizard/v*.*.*' + +permissions: + contents: write + packages: write + +jobs: + publish: + name: Build and Publish Wizard Binaries + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: Extract Version + id: vars + run: | + TAG_REF="${GITHUB_REF#refs/tags/}" + VERSION=$(echo "$TAG_REF" | grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+') + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + + - name: Build Binaries + run: | + mkdir -p build + GOOS=linux GOARCH=amd64 go build -o build/honeywire-linux-amd64 wizard/cmd/wizard/main.go + GOOS=linux GOARCH=arm64 go build -o build/honeywire-linux-arm64 wizard/cmd/wizard/main.go + + - name: Generate Checksums + run: | + cd build + sha256sum honeywire-* > checksums.txt + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: "Wizard v${{ steps.vars.outputs.VERSION }}" + files: | + build/honeywire-linux-amd64 + build/honeywire-linux-arm64 + build/checksums.txt + generate_release_notes: true diff --git a/.github/workflows/rollback-sensor-registry.yml b/.github/workflows/rollback-sensor-registry.yml new file mode 100644 index 0000000..33d72fe --- /dev/null +++ b/.github/workflows/rollback-sensor-registry.yml @@ -0,0 +1,102 @@ +name: Rollback Sensor Registry + +on: + workflow_dispatch: + inputs: + sensor_name: + description: 'Sensor name to rollback (e.g. file-canary) or "all" to rollback all sensors' + required: true + default: 'all' + +jobs: + rollback: + name: "🔄 Rollback Sensor Version" + runs-on: ubuntu-latest + steps: + - name: Checkout main branch + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Prepare build script + run: | + cp scripts/build-registry-index.sh /tmp/build-registry-index.sh + chmod +x /tmp/build-registry-index.sh + + - name: Checkout registry-pages branch + run: | + git config user.name "HoneyWire CI" + git config user.email "ci@honeywire.dev" + git fetch origin registry-pages + git checkout registry-pages + + - name: Perform Rollback + id: rollback + env: + SENSOR_NAME: ${{ inputs.sensor_name }} + run: | + SENSOR_TARGET="$SENSOR_NAME" + + if [ ! -f index.json ]; then + echo "❌ index.json not found in registry-pages branch!" + exit 1 + fi + + SENSORS=() + if [ "$SENSOR_TARGET" = "all" ]; then + # Extract sensor names from index.json (stripping the "hw-sensor-" prefix) + mapfile -t SENSORS < <(jq -r '.sensors[] | .id | sub("^hw-sensor-"; "")' index.json) + else + SENSORS=("$SENSOR_TARGET") + fi + + echo "Rolling back sensors: ${SENSORS[*]}" + + ROLLBACK_COUNT=0 + + for SENSOR in "${SENSORS[@]}"; do + # Find the highest version currently in the index for this sensor + LATEST_VERSION=$(jq -r --arg id "hw-sensor-$SENSOR" '.sensors[] | select(.id == $id) | .versions | map(.v) | sort_by(split(".") | map(tonumber)) | last' index.json) + + if [ -z "$LATEST_VERSION" ] || [ "$LATEST_VERSION" = "null" ]; then + echo "⚠️ No versions found for sensor $SENSOR, skipping." + continue + fi + + FILE_TO_DELETE="${SENSOR}-v${LATEST_VERSION}.json" + if [ -f "$FILE_TO_DELETE" ]; then + # Mark as deprecated instead of deleting + jq '.deprecated = true' "$FILE_TO_DELETE" > "$FILE_TO_DELETE.tmp" && mv "$FILE_TO_DELETE.tmp" "$FILE_TO_DELETE" + echo "✅ Marked $FILE_TO_DELETE as deprecated" + + ROLLBACK_COUNT=$((ROLLBACK_COUNT + 1)) + else + echo "⚠️ File $FILE_TO_DELETE not found, treating as manual deletion." + ROLLBACK_COUNT=$((ROLLBACK_COUNT + 1)) + fi + done + + if [ "$ROLLBACK_COUNT" -eq 0 ]; then + echo "⚠️ No rollbacks performed." + fi + + echo "count=$ROLLBACK_COUNT" >> $GITHUB_OUTPUT + + - name: Rebuild index.json + if: steps.rollback.outputs.count > 0 + run: | + /tmp/build-registry-index.sh + echo "✅ index.json regenerated" + + - name: Commit and push changes + if: steps.rollback.outputs.count > 0 + env: + SENSOR_NAME: ${{ inputs.sensor_name }} + run: | + git add *.json + git commit -m "registry: rollback $SENSOR_NAME + + Automated rollback of the latest versions via deprecation." + + git push origin registry-pages + echo "🎉 Rollback complete!" diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..4e19e16 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,52 @@ +name: Security Scans + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + workflow_dispatch: + +permissions: + security-events: write + actions: read + contents: read + +jobs: + analyze: + name: CodeQL Analysis + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + language: [ 'go', 'javascript', 'python' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + + trivy: + name: Trivy FS Scan + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + ignore-unfixed: true + format: 'table' + severity: 'CRITICAL,HIGH' diff --git a/.github/workflows/sensor_pipeline.yml b/.github/workflows/sensor_pipeline.yml deleted file mode 100644 index 2e7a2dc..0000000 --- a/.github/workflows/sensor_pipeline.yml +++ /dev/null @@ -1,178 +0,0 @@ -name: Official Sensor Build & Publish - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -on: - push: - branches: [ "main" ] - tags: [ "v*.*.*" ] - paths: [ 'Sensors/official/**' ] - pull_request: - paths: [ 'Sensors/official/**' ] - workflow_dispatch: - -jobs: - # ========================================== - # STAGE 1: DYNAMIC MATRIX GENERATOR - # ========================================== - detect-sensors: - name: "🔍 Detect Changed Official Sensors" - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.generate-matrix.outputs.matrix }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Get changed directories - id: changed-files - uses: tj-actions/changed-files@v46 - with: - files: Sensors/official/** - dir_names: true - dir_names_max_depth: 3 - json: true - escape_json: false - - - name: Generate Independent Matrix - id: generate-matrix - env: - ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} - run: | - # NEW: If it's a manual trigger OR a new Tag Release, build ALL sensors - if [ "${{ github.event_name }}" == "workflow_dispatch" ] || [ "${{ github.ref_type }}" == "tag" ]; then - MATRIX_JSON=$(find Sensors/official -mindepth 1 -maxdepth 1 -type d -exec test -f '{}/Dockerfile' \; -print | awk -F/ '{print $3}' | jq -R -s -c 'split("\n")[:-1]') - else - # On Push/PR: Parse the changed files JSON from the safe environment variable - if [ "$ALL_CHANGED_FILES" == "[]" ] || [ -z "$ALL_CHANGED_FILES" ]; then - MATRIX_JSON="[]" - else - MATRIX_JSON=$(echo "$ALL_CHANGED_FILES" | jq -c '[.[] | sub("Sensors/official/"; "")]') - fi - fi - - echo "Generated Matrix: $MATRIX_JSON" - echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT - - # ========================================== - # STAGE 2: PARALLEL TEST, SCAN, & PUBLISH - # ========================================== - process-sensors: - name: "🚀 Process: ${{ matrix.sensor }}" - needs: detect-sensors - if: ${{ needs.detect-sensors.outputs.matrix != '[]' && needs.detect-sensors.outputs.matrix != '' }} - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - sensor: ${{ fromJson(needs.detect-sensors.outputs.matrix) }} - - permissions: - contents: read - packages: write - id-token: write - security-events: write # REQUIRED FOR CODEQL TO UPLOAD ALERTS - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.25' - cache-dependency-path: Sensors/official/${{ matrix.sensor }}/go.sum - - - name: Run Go Tests - working-directory: Sensors/official/${{ matrix.sensor }} - run: | - if [ -f "go.mod" ]; then - go test -v ./... - else - echo "No go.mod found, skipping native Go tests." - fi - - # ---------------------------------------------------- - # CODEQL SAST SCAN - # ---------------------------------------------------- - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: 'go' - - - name: Build for CodeQL - working-directory: Sensors/official/${{ matrix.sensor }} - run: | - if [ -f "go.mod" ]; then - go build -v ./... - fi - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - # Crucial for monorepos: prevents matrix jobs from overwriting each other's alerts - category: "/language:go/sensor:${{ matrix.sensor }}" - - # ---------------------------------------------------- - # TRIVY FILESYSTEM SCA SCAN - # ---------------------------------------------------- - - name: Run Trivy FS Scan - uses: aquasecurity/trivy-action@master - with: - scan-type: 'fs' - scan-ref: Sensors/official/${{ matrix.sensor }} - format: 'table' - exit-code: '1' - severity: 'CRITICAL,HIGH' - - # ---------------------------------------------------- - # PUBLISHING STEPS (Only runs on Main Branch or Manual Trigger) - # ---------------------------------------------------- - - name: Set up QEMU (ARM Support) - if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' - uses: docker/setup-buildx-action@v3 - - - name: Log in to GHCR - if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract Docker metadata - if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' || github.ref_type == 'tag' - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository_owner }}/honeywire-${{ matrix.sensor }} - tags: | - type=raw,value=latest,enable={{is_default_branch}} - type=semver,pattern={{version}} - - - name: Build and Push - if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' - uses: docker/build-push-action@v5 - with: - context: . - file: Sensors/official/${{ matrix.sensor }}/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha,scope=${{ matrix.sensor }} - cache-to: type=gha,mode=max,scope=${{ matrix.sensor }} - provenance: true - sbom: true - - - name: Summary - run: | - echo "### ✅ ${{ matrix.sensor }} Pipeline Complete" >> $GITHUB_STEP_SUMMARY \ No newline at end of file