diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml new file mode 100644 index 0000000..abe17b2 --- /dev/null +++ b/.github/workflows/build-and-upload.yml @@ -0,0 +1,46 @@ +name: Build and Upload Artifacts + +# SHA pins below are managed by Dependabot/Renovate — do not replace with tags. +on: + workflow_call: + +jobs: + release: + runs-on: ubuntu-latest + permissions: + packages: write + contents: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + with: + go-version-file: go.mod + cache: "none" + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: '~> v2' + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Install gooci cli + run: go install github.com/compliance-framework/gooci@v0.0.7 + - name: Authenticate gooci cli + env: + GOOCI_ACTOR: ${{ github.actor }} + GOOCI_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gooci login ghcr.io --username "$GOOCI_ACTOR" --password "$GOOCI_TOKEN" + - name: gooci Upload Version + env: + UPLOAD_OWNER: ${{ github.repository_owner }} + UPLOAD_REPO: ${{ github.event.repository.name }} + UPLOAD_REF: ${{ github.ref_name }} + run: gooci upload dist/ "ghcr.io/${UPLOAD_OWNER}/${UPLOAD_REPO}:${UPLOAD_REF}" + - name: gooci Upload Latest + if: "!contains(github.ref_name, '-')" + env: + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} + run: gooci upload dist/ "ghcr.io/${OWNER}/${REPO}:latest" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index b6b1fa0..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: CI - -on: - push: - branches: ["**"] - pull_request: - branches: ["**"] - -permissions: - contents: read - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - name: go vet - run: go vet ./... - - name: go test - run: go test ./... diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000..c6b883f --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,13 @@ +name: Push + +on: + pull_request: + push: + branches: + - '**' + +jobs: + test: + permissions: + contents: read + uses: ./.github/workflows/test.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a84b419..64a1faf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,30 +7,4 @@ on: jobs: release: - runs-on: ubuntu-latest - permissions: - packages: write - contents: write - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: - persist-credentials: false - - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff - with: - go-version-file: go.mod - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a - with: - version: '~> v2' - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Install gooci cli - run: go install github.com/compliance-framework/gooci@v0.0.7 - - name: Authenticate gooci cli - run: gooci login ghcr.io --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} - - name: gooci Upload Version - run: gooci upload dist/ ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ github.ref_name }} - - name: gooci Upload Latest - if: "!contains(github.ref_name, '-')" - run: gooci upload dist/ ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest + uses: ./.github/workflows/build-and-upload.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..ff021be --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Go Test + +on: + workflow_call: + +jobs: + test: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + with: + go-version-file: go.mod + + - name: Vet + run: go vet ./... + + - name: Test + run: go test ./... diff --git a/main.go b/main.go index 3fcce6c..44158ed 100644 --- a/main.go +++ b/main.go @@ -100,20 +100,14 @@ func (l *CompliancePlugin) Eval(request *proto.EvalRequest, apiHelper runner.Api } // Scope policy paths to each resource type using behavior mapping. - // Default: a bundle named "*ecr-repository-policies*" covers repository/registry checks; - // one named "*ecr-image-policies*" covers image checks. A single bundle containing all - // policies (e.g. plugin-aws-ecr-policies) is mapped to all three behaviors so it - // continues to work unchanged; operators may override by supplying two separate bundles - // and configuring PolicyBehavior in the agent config. + // Bundles are matched by substring against the policy path, so a bundle + // named "*ecr-repository-policies*" maps to repository checks, etc. defaultBehaviorMapping := map[string][]string{ - "ecr-repository-policies": {"repository", "registry"}, + "ecr-repository-policies": {"repository"}, + "ecr-registry-policies": {"registry"}, "ecr-image-policies": {"image"}, - // Single all-in-one bundle: cover every behavior so it works out of the box. - "plugin-aws-ecr-policies": {"repository", "registry", "image"}, } - policyEval := request. - WithDefaultPolicyBehavior(defaultBehaviorMapping). - WithUndefinedMappedTo([]string{"repository", "registry"}) + policyEval := request.WithDefaultPolicyBehavior(defaultBehaviorMapping) repositoryPaths := policyEval.PolicyPathsForBehavior("repository") registryPaths := policyEval.PolicyPathsForBehavior("registry") @@ -126,15 +120,16 @@ func (l *CompliancePlugin) Eval(request *proto.EvalRequest, apiHelper runner.Api var evalErrors error for _, region := range l.config.Regions { - // CONFIG — repository checks - repos, err := dataFetcher.FetchRepositories(ctx, region) - if err != nil { - return &proto.EvalResponse{Status: proto.ExecutionStatus_FAILURE}, fmt.Errorf("region %s: fetching repositories: %w", region, err) + // Fetch repositories only when at least one bundle covers repository or image checks. + var repos []internal.RepositoryContext + if len(repositoryPaths) > 0 || len(imagePaths) > 0 { + r, err := dataFetcher.FetchRepositories(ctx, region) + if err != nil { + return &proto.EvalResponse{Status: proto.ExecutionStatus_FAILURE}, fmt.Errorf("region %s: fetching repositories: %w", region, err) + } + repos = internal.FilterByAccounts(r, l.config.Accounts) } - // Filter to configured accounts if any are specified. - repos = internal.FilterByAccounts(repos, l.config.Accounts) - for _, repo := range repos { evidences, err := policyEvaluator.EvalRepository(ctx, repo, repositoryPaths, l.policyData, l.config.PolicyLabels) allEvidences = append(allEvidences, evidences...)