diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml deleted file mode 100644 index f4dd62ca..00000000 --- a/.github/workflows/build-images.yml +++ /dev/null @@ -1,196 +0,0 @@ -name: Build and Push Container Images - -on: - push: - branches: - - main - - develop - tags: - - 'v*' - pull_request: - branches: - - main - - develop - workflow_dispatch: # Allow manual triggering - -env: - REGISTRY: ghcr.io - IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }} - -jobs: - build-controller: - name: Build Controller Image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.IMAGE_PREFIX }}/streamspace-controller - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha,prefix={{branch}}- - type=raw,value=latest,enable={{is_default_branch}} - - - name: Build and push Controller image - uses: docker/build-push-action@v5 - with: - context: ./controller - file: ./controller/Dockerfile - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - platforms: linux/amd64,linux/arm64 - - build-api: - name: Build API Image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.IMAGE_PREFIX }}/streamspace-api - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha,prefix={{branch}}- - type=raw,value=latest,enable={{is_default_branch}} - - - name: Build and push API image - uses: docker/build-push-action@v5 - with: - context: ./api - file: ./api/Dockerfile - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - platforms: linux/amd64,linux/arm64 - - build-ui: - name: Build UI Image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.IMAGE_PREFIX }}/streamspace-ui - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha,prefix={{branch}}- - type=raw,value=latest,enable={{is_default_branch}} - - - name: Build and push UI image - uses: docker/build-push-action@v5 - with: - context: ./ui - file: ./ui/Dockerfile - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - platforms: linux/amd64,linux/arm64 - - create-release: - name: Create Release Summary - runs-on: ubuntu-latest - needs: [build-controller, build-api, build-ui] - if: startsWith(github.ref, 'refs/tags/v') - - steps: - - name: Create Release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref_name }} - release_name: StreamSpace ${{ github.ref_name }} - body: | - ## StreamSpace ${{ github.ref_name }} - - Container images published to GitHub Container Registry: - - - `ghcr.io/${{ github.repository_owner }}/streamspace-controller:${{ github.ref_name }}` - - `ghcr.io/${{ github.repository_owner }}/streamspace-api:${{ github.ref_name }}` - - `ghcr.io/${{ github.repository_owner }}/streamspace-ui:${{ github.ref_name }}` - - ### Installation - - ```bash - helm install streamspace ./chart \\ - --set controller.image.tag=${{ github.ref_name }} \\ - --set api.image.tag=${{ github.ref_name }} \\ - --set ui.image.tag=${{ github.ref_name }} - ``` - - See [CHANGELOG.md](CHANGELOG.md) for full details. - draft: false - prerelease: false diff --git a/.github/workflows/container-images.yml b/.github/workflows/container-images.yml new file mode 100644 index 00000000..805a37a4 --- /dev/null +++ b/.github/workflows/container-images.yml @@ -0,0 +1,524 @@ +name: Container Images - Build, Sign & Publish + +on: + push: + branches: + - main + - develop + tags: + - 'v*' + paths: + - 'api/**' + - 'controller/**' + - 'ui/**' + - '.github/workflows/container-images.yml' + pull_request: + branches: + - main + - develop + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }}/streamspace + +permissions: + contents: write + packages: write + id-token: write + security-events: write + attestations: write + +jobs: + build-and-sign-controller: + name: Build & Sign Controller + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Install Cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@v3 + with: + cosign-release: 'v2.2.2' + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_PREFIX }}-controller + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Set build variables + id: vars + run: | + echo "VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}" >> $GITHUB_OUTPUT + echo "COMMIT=${{ github.sha }}" >> $GITHUB_OUTPUT + echo "BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT + + - name: Build and push Controller image + id: build + uses: docker/build-push-action@v5 + with: + context: ./controller + file: ./controller/Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VERSION=${{ steps.vars.outputs.VERSION }} + COMMIT=${{ steps.vars.outputs.COMMIT }} + BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: true + sbom: true + + - name: Sign Controller image + if: github.event_name != 'pull_request' + env: + COSIGN_EXPERIMENTAL: "true" + run: | + echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build.outputs.digest }} + + - name: Generate SBOM for Controller + if: github.event_name != 'pull_request' + uses: anchore/sbom-action@v0 + with: + path: ./controller + artifact-name: streamspace-controller-sbom.spdx.json + output-file: sbom-controller.spdx.json + format: spdx-json + + - name: Attest Controller SBOM + if: github.event_name != 'pull_request' + env: + COSIGN_EXPERIMENTAL: "true" + run: | + cosign attest --yes --type spdxjson \ + --predicate sbom-controller.spdx.json \ + ${{ env.IMAGE_PREFIX }}-controller@${{ steps.build.outputs.digest }} + + - name: Upload Controller SBOM + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: sbom-controller + path: sbom-controller.spdx.json + retention-days: 90 + + build-and-sign-api: + name: Build & Sign API + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Install Cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@v3 + with: + cosign-release: 'v2.2.2' + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_PREFIX }}-api + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Set build variables + id: vars + run: | + echo "VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}" >> $GITHUB_OUTPUT + echo "COMMIT=${{ github.sha }}" >> $GITHUB_OUTPUT + echo "BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT + + - name: Build and push API image + id: build + uses: docker/build-push-action@v5 + with: + context: ./api + file: ./api/Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VERSION=${{ steps.vars.outputs.VERSION }} + COMMIT=${{ steps.vars.outputs.COMMIT }} + BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: true + sbom: true + + - name: Sign API image + if: github.event_name != 'pull_request' + env: + COSIGN_EXPERIMENTAL: "true" + run: | + echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build.outputs.digest }} + + - name: Generate SBOM for API + if: github.event_name != 'pull_request' + uses: anchore/sbom-action@v0 + with: + path: ./api + artifact-name: streamspace-api-sbom.spdx.json + output-file: sbom-api.spdx.json + format: spdx-json + + - name: Attest API SBOM + if: github.event_name != 'pull_request' + env: + COSIGN_EXPERIMENTAL: "true" + run: | + cosign attest --yes --type spdxjson \ + --predicate sbom-api.spdx.json \ + ${{ env.IMAGE_PREFIX }}-api@${{ steps.build.outputs.digest }} + + - name: Upload API SBOM + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: sbom-api + path: sbom-api.spdx.json + retention-days: 90 + + build-and-sign-ui: + name: Build & Sign UI + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Install Cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@v3 + with: + cosign-release: 'v2.2.2' + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_PREFIX }}-ui + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Set build variables + id: vars + run: | + echo "VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}" >> $GITHUB_OUTPUT + echo "COMMIT=${{ github.sha }}" >> $GITHUB_OUTPUT + echo "BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT + + - name: Build and push UI image + id: build + uses: docker/build-push-action@v5 + with: + context: ./ui + file: ./ui/Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VERSION=${{ steps.vars.outputs.VERSION }} + COMMIT=${{ steps.vars.outputs.COMMIT }} + BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: true + sbom: true + + - name: Sign UI image + if: github.event_name != 'pull_request' + env: + COSIGN_EXPERIMENTAL: "true" + run: | + echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build.outputs.digest }} + + - name: Generate SBOM for UI + if: github.event_name != 'pull_request' + uses: anchore/sbom-action@v0 + with: + path: ./ui + artifact-name: streamspace-ui-sbom.spdx.json + output-file: sbom-ui.spdx.json + format: spdx-json + + - name: Attest UI SBOM + if: github.event_name != 'pull_request' + env: + COSIGN_EXPERIMENTAL: "true" + run: | + cosign attest --yes --type spdxjson \ + --predicate sbom-ui.spdx.json \ + ${{ env.IMAGE_PREFIX }}-ui@${{ steps.build.outputs.digest }} + + - name: Upload UI SBOM + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: sbom-ui + path: sbom-ui.spdx.json + retention-days: 90 + + security-scan: + name: Security Scan + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + needs: [build-and-sign-controller, build-and-sign-api, build-and-sign-ui] + strategy: + matrix: + component: [controller, api, ui] + steps: + - name: Install Cosign + uses: sigstore/cosign-installer@v3 + with: + cosign-release: 'v2.2.2' + + - name: Verify image signature + env: + COSIGN_EXPERIMENTAL: "true" + run: | + cosign verify ${{ env.IMAGE_PREFIX }}-${{ matrix.component }}:latest + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.IMAGE_PREFIX }}-${{ matrix.component }}:latest + format: 'sarif' + output: 'trivy-${{ matrix.component }}-results.sarif' + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy results to GitHub Security + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: 'trivy-${{ matrix.component }}-results.sarif' + category: 'trivy-${{ matrix.component }}' + + - name: Generate Trivy report + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.IMAGE_PREFIX }}-${{ matrix.component }}:latest + format: 'table' + severity: 'CRITICAL,HIGH,MEDIUM' + + update-helm-chart: + name: Update Helm Chart + runs-on: ubuntu-latest + needs: [build-and-sign-controller, build-and-sign-api, build-and-sign-ui] + if: startsWith(github.ref, 'refs/tags/v') + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract version + id: version + run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + + - name: Update Chart.yaml + run: | + VERSION=${{ steps.version.outputs.VERSION }} + sed -i "s/^version:.*/version: ${VERSION#v}/" chart/Chart.yaml + sed -i "s/^appVersion:.*/appVersion: \"${VERSION}\"/" chart/Chart.yaml + + - name: Update values.yaml + run: | + VERSION=${{ steps.version.outputs.VERSION }} + sed -i "s/tag: \".*\"/tag: \"${VERSION}\"/" chart/values.yaml + + - name: Commit changes + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add chart/Chart.yaml chart/values.yaml + git commit -m "chore: bump chart version to ${{ steps.version.outputs.VERSION }}" || echo "No changes to commit" + git push origin HEAD:main || echo "No changes to push" + + create-release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: [security-scan, update-helm-chart] + if: startsWith(github.ref, 'refs/tags/v') + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract version + id: version + run: | + VERSION=${GITHUB_REF#refs/tags/} + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + echo "VERSION_NUM=${VERSION#v}" >> $GITHUB_OUTPUT + + - name: Generate changelog + id: changelog + run: | + PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + if [ -z "$PREV_TAG" ]; then + CHANGELOG=$(git log --pretty=format:"- %s (%h)" --no-merges) + else + CHANGELOG=$(git log ${PREV_TAG}..HEAD --pretty=format:"- %s (%h)" --no-merges) + fi + echo "$CHANGELOG" > CHANGELOG.txt + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: 'v3.14.0' + + - name: Package Helm chart + run: | + helm package chart/ --version ${{ steps.version.outputs.VERSION_NUM }} --app-version ${{ steps.version.outputs.VERSION }} + mv streamspace-${{ steps.version.outputs.VERSION_NUM }}.tgz streamspace-helm-chart.tgz + + - name: Download SBOMs + uses: actions/download-artifact@v4 + with: + path: sboms + + - name: Create Release Notes + run: | + cat > RELEASE_NOTES.md <> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Images" >> $GITHUB_STEP_SUMMARY + echo "- ✅ \`${{ env.IMAGE_PREFIX }}-controller:latest\`" >> $GITHUB_STEP_SUMMARY + echo "- ✅ \`${{ env.IMAGE_PREFIX }}-api:latest\`" >> $GITHUB_STEP_SUMMARY + echo "- ✅ \`${{ env.IMAGE_PREFIX }}-ui:latest\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Platforms" >> $GITHUB_STEP_SUMMARY + echo "- linux/amd64" >> $GITHUB_STEP_SUMMARY + echo "- linux/arm64" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Security Features" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Signed with Cosign" >> $GITHUB_STEP_SUMMARY + echo "- ✅ SBOM generated" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Provenance attestation" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Vulnerability scanned" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index 3a9ba781..00000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,226 +0,0 @@ -name: Docker Build and Push - -on: - push: - branches: - - main - tags: - - 'v*' - workflow_dispatch: - -env: - REGISTRY: ghcr.io - ORG_NAME: ${{ github.repository_owner }} - -jobs: - build-controller: - name: Build and Push Controller - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.ORG_NAME }}/streamspace-controller - tags: | - type=ref,event=branch - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha - type=raw,value=latest,enable={{is_default_branch}} - - - name: Set build variables - id: vars - run: | - echo "VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}" >> $GITHUB_OUTPUT - echo "COMMIT=${{ github.sha }}" >> $GITHUB_OUTPUT - echo "BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: ./controller - file: ./controller/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - VERSION=${{ steps.vars.outputs.VERSION }} - COMMIT=${{ steps.vars.outputs.COMMIT }} - BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} - cache-from: type=gha - cache-to: type=gha,mode=max - - build-api: - name: Build and Push API - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.ORG_NAME }}/streamspace-api - tags: | - type=ref,event=branch - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha - type=raw,value=latest,enable={{is_default_branch}} - - - name: Set build variables - id: vars - run: | - echo "VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}" >> $GITHUB_OUTPUT - echo "COMMIT=${{ github.sha }}" >> $GITHUB_OUTPUT - echo "BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: ./api - file: ./api/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - VERSION=${{ steps.vars.outputs.VERSION }} - COMMIT=${{ steps.vars.outputs.COMMIT }} - BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} - cache-from: type=gha - cache-to: type=gha,mode=max - - build-ui: - name: Build and Push UI - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.ORG_NAME }}/streamspace-ui - tags: | - type=ref,event=branch - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha - type=raw,value=latest,enable={{is_default_branch}} - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: ./ui - file: ./ui/Dockerfile - 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 - - update-helm-values: - name: Update Helm Chart Version - runs-on: ubuntu-latest - needs: [build-controller, build-api, build-ui] - if: startsWith(github.ref, 'refs/tags/v') - permissions: - contents: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract version - id: version - run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - - - name: Update Chart.yaml - run: | - VERSION=${{ steps.version.outputs.VERSION }} - sed -i "s/^version:.*/version: ${VERSION#v}/" chart/Chart.yaml - sed -i "s/^appVersion:.*/appVersion: \"${VERSION}\"/" chart/Chart.yaml - - - name: Update values.yaml - run: | - VERSION=${{ steps.version.outputs.VERSION }} - sed -i "s/tag: \"v.*\"/tag: \"${VERSION}\"/" chart/values.yaml - - - name: Commit changes - run: | - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add chart/Chart.yaml chart/values.yaml - git commit -m "chore: bump chart version to ${{ steps.version.outputs.VERSION }}" || echo "No changes to commit" - git push origin HEAD:main || echo "No changes to push" - - summary: - name: Build Summary - runs-on: ubuntu-latest - needs: [build-controller, build-api, build-ui] - if: always() - steps: - - name: Generate summary - run: | - echo "## Docker Build Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Images Built" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY }}/${{ env.ORG_NAME }}/streamspace-controller:latest\`" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY }}/${{ env.ORG_NAME }}/streamspace-api:latest\`" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY }}/${{ env.ORG_NAME }}/streamspace-ui:latest\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Platforms" >> $GITHUB_STEP_SUMMARY - echo "- linux/amd64" >> $GITHUB_STEP_SUMMARY - echo "- linux/arm64" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/image-signing.yml b/.github/workflows/image-signing.yml deleted file mode 100644 index cf77a071..00000000 --- a/.github/workflows/image-signing.yml +++ /dev/null @@ -1,337 +0,0 @@ -# Container Image Signing with Cosign -# Signs all StreamSpace container images for supply chain security - -name: Sign Container Images - -on: - push: - branches: [main] - paths: - - 'api/**' - - 'controller/**' - - 'ui/**' - - '.github/workflows/image-signing.yml' - pull_request: - branches: [main] - release: - types: [published] - workflow_dispatch: - -env: - REGISTRY: ghcr.io - IMAGE_PREFIX: ${{ github.repository_owner }}/streamspace - -permissions: - contents: read - packages: write - id-token: write # Required for OIDC token for Cosign - -jobs: - build-and-sign-api: - name: Build and Sign API Image - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Install Cosign - uses: sigstore/cosign-installer@v3 - with: - cosign-release: 'v2.2.2' - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha - type=raw,value=latest,enable={{is_default_branch}} - - - name: Build and push API image - id: build-api - uses: docker/build-push-action@v5 - with: - context: ./api - file: ./api/Dockerfile - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - platforms: linux/amd64,linux/arm64 - provenance: true - sbom: true - - - name: Sign API image with Cosign - if: github.event_name != 'pull_request' - env: - COSIGN_EXPERIMENTAL: "true" - run: | - echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-api.outputs.digest }} - - - name: Verify API image signature - if: github.event_name != 'pull_request' - env: - COSIGN_EXPERIMENTAL: "true" - run: | - echo "${{ steps.meta.outputs.tags }}" | head -n 1 | xargs -I {} cosign verify {}@${{ steps.build-api.outputs.digest }} - - build-and-sign-controller: - name: Build and Sign Controller Image - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Install Cosign - uses: sigstore/cosign-installer@v3 - with: - cosign-release: 'v2.2.2' - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-controller - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha - type=raw,value=latest,enable={{is_default_branch}} - - - name: Build and push Controller image - id: build-controller - uses: docker/build-push-action@v5 - with: - context: ./controller - file: ./controller/Dockerfile - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - platforms: linux/amd64,linux/arm64 - provenance: true - sbom: true - - - name: Sign Controller image with Cosign - if: github.event_name != 'pull_request' - env: - COSIGN_EXPERIMENTAL: "true" - run: | - echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-controller.outputs.digest }} - - - name: Verify Controller image signature - if: github.event_name != 'pull_request' - env: - COSIGN_EXPERIMENTAL: "true" - run: | - echo "${{ steps.meta.outputs.tags }}" | head -n 1 | xargs -I {} cosign verify {}@${{ steps.build-controller.outputs.digest }} - - build-and-sign-ui: - name: Build and Sign UI Image - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Install Cosign - uses: sigstore/cosign-installer@v3 - with: - cosign-release: 'v2.2.2' - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ui - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha - type=raw,value=latest,enable={{is_default_branch}} - - - name: Build and push UI image - id: build-ui - uses: docker/build-push-action@v5 - with: - context: ./ui - file: ./ui/Dockerfile - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - platforms: linux/amd64,linux/arm64 - provenance: true - sbom: true - - - name: Sign UI image with Cosign - if: github.event_name != 'pull_request' - env: - COSIGN_EXPERIMENTAL: "true" - run: | - echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-ui.outputs.digest }} - - - name: Verify UI image signature - if: github.event_name != 'pull_request' - env: - COSIGN_EXPERIMENTAL: "true" - run: | - echo "${{ steps.meta.outputs.tags }}" | head -n 1 | xargs -I {} cosign verify {}@${{ steps.build-ui.outputs.digest }} - - generate-attestations: - name: Generate SLSA Attestations - runs-on: ubuntu-latest - if: github.event_name != 'pull_request' - needs: [build-and-sign-api, build-and-sign-controller, build-and-sign-ui] - permissions: - contents: read - packages: write - id-token: write - attestations: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Cosign - uses: sigstore/cosign-installer@v3 - with: - cosign-release: 'v2.2.2' - - - name: Generate SBOM for API - uses: anchore/sbom-action@v0 - with: - path: ./api - artifact-name: streamspace-api-sbom.spdx.json - output-file: sbom-api.spdx.json - format: spdx-json - - - name: Generate SBOM for Controller - uses: anchore/sbom-action@v0 - with: - path: ./controller - artifact-name: streamspace-controller-sbom.spdx.json - output-file: sbom-controller.spdx.json - format: spdx-json - - - name: Generate SBOM for UI - uses: anchore/sbom-action@v0 - with: - path: ./ui - artifact-name: streamspace-ui-sbom.spdx.json - output-file: sbom-ui.spdx.json - format: spdx-json - - - name: Upload SBOMs as artifacts - uses: actions/upload-artifact@v4 - with: - name: sboms - path: | - sbom-*.spdx.json - retention-days: 90 - - - name: Attest API SBOM - env: - COSIGN_EXPERIMENTAL: "true" - run: | - cosign attest --yes --type spdxjson \ - --predicate sbom-api.spdx.json \ - ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api:latest - - - name: Attest Controller SBOM - env: - COSIGN_EXPERIMENTAL: "true" - run: | - cosign attest --yes --type spdxjson \ - --predicate sbom-controller.spdx.json \ - ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-controller:latest - - - name: Attest UI SBOM - env: - COSIGN_EXPERIMENTAL: "true" - run: | - cosign attest --yes --type spdxjson \ - --predicate sbom-ui.spdx.json \ - ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ui:latest - - security-scan: - name: Security Scan Signed Images - runs-on: ubuntu-latest - if: github.event_name != 'pull_request' - needs: [build-and-sign-api, build-and-sign-controller, build-and-sign-ui] - strategy: - matrix: - component: [api, controller, ui] - steps: - - name: Install Cosign - uses: sigstore/cosign-installer@v3 - with: - cosign-release: 'v2.2.2' - - - name: Verify image signature - env: - COSIGN_EXPERIMENTAL: "true" - run: | - cosign verify ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.component }}:latest - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.component }}:latest - format: 'sarif' - output: 'trivy-${{ matrix.component }}-results.sarif' - severity: 'CRITICAL,HIGH' - - - name: Upload Trivy results to GitHub Security - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: 'trivy-${{ matrix.component }}-results.sarif' - category: 'trivy-${{ matrix.component }}' - - - name: Fail on critical vulnerabilities - uses: aquasecurity/trivy-action@master - with: - image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.component }}:latest - format: 'table' - exit-code: '1' - severity: 'CRITICAL' diff --git a/api/Dockerfile b/api/Dockerfile index 8a0e7564..c33e8433 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -5,6 +5,8 @@ FROM golang:1.21-alpine AS builder ARG VERSION=dev ARG COMMIT=unknown ARG BUILD_DATE +# Docker Buildx automatically provides TARGETARCH +ARG TARGETARCH # Install build dependencies RUN apk add --no-cache git make ca-certificates @@ -21,8 +23,8 @@ RUN go mod download COPY cmd/ cmd/ COPY internal/ internal/ -# Build the API server with version info -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a \ +# Build the API server with version info for the target architecture +RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -a \ -ldflags "-w -s -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.buildDate=${BUILD_DATE}" \ -o api-server cmd/main.go diff --git a/controller/Dockerfile b/controller/Dockerfile index e2005fde..42c5e22f 100644 --- a/controller/Dockerfile +++ b/controller/Dockerfile @@ -5,6 +5,8 @@ FROM golang:1.21 AS builder ARG VERSION=dev ARG COMMIT=unknown ARG BUILD_DATE +# Docker Buildx automatically provides TARGETARCH +ARG TARGETARCH WORKDIR /workspace @@ -20,8 +22,8 @@ COPY api/ api/ COPY controllers/ controllers/ COPY pkg/ pkg/ -# Build the controller with version info -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a \ +# Build the controller with version info for the target architecture +RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -a \ -ldflags "-w -s -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.buildDate=${BUILD_DATE}" \ -o manager cmd/main.go