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
2 changes: 1 addition & 1 deletion .coverage-baseline
Original file line number Diff line number Diff line change
@@ -1 +1 @@
76.5
76.6
2 changes: 1 addition & 1 deletion .github/RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Push to main

4. **When Release PR is Merged**:
- GitHub Release created with tag (e.g., `v0.2.0`) — first as a **draft**, so every signed
asset (`install.yaml`, SBOM, `.sigstore.json` signatures, `.intoto.jsonl` attestations)
asset (`crds.yaml`, `install.yaml`, SBOM, `.sigstore.json` signatures, `.intoto.jsonl` attestations)
can be attached before it goes public; immutable releases reject post-publish uploads, so
`publish-release` flips the draft to published only after every asset is in place.
- The linux/amd64 + linux/arm64 image digests already built and scanned by that
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ jobs:
with:
name: release-bundle
path: |
dist/crds.yaml
dist/install.yaml
gitops-reverser.tgz
if-no-files-found: error
Expand Down Expand Up @@ -809,7 +810,7 @@ jobs:
# place gitops-reverser mirrors REMOTE clusters (GitTarget.spec.kubeConfig).
# kcp workspaces are cheap logical clusters, so its own runner installs a
# small kcp control plane and provisions three workspaces as source clusters.
# docs/design/config-plane-split.md.
# docs/finished/config-plane-split.md.
#
# `task test-e2e-source-cluster` pins its own label filter and Ginkgo procs
# (SOURCE_CLUSTER_GINKGO_PROCS=1 — the specs share one kcp port-forward), so
Expand Down
30 changes: 25 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,11 @@ jobs:
sbom.spdx.json.intoto.jsonl
body: |
## Installation
### Quick Install (Single YAML)
### Quick Install (plain manifests)
The CRDs ship as their own file and must be applied **first** — the bundle
contains a custom resource that cannot be created before its CRD exists:
```bash
kubectl apply -f https://github.com/ConfigButler/gitops-reverser/releases/download/${{ needs.release-please.outputs.tag_name }}/crds.yaml
kubectl apply -f https://github.com/ConfigButler/gitops-reverser/releases/download/${{ needs.release-please.outputs.tag_name }}/install.yaml
```
### Helm Chart
Expand Down Expand Up @@ -314,12 +317,17 @@ jobs:

# The chart is already signed above via its OCI digest, but OpenSSF
# Scorecard's Signed-Releases check only looks at the GitHub *release
# assets*, not the OCI registry. Sign and attest install.yaml directly so
# the release carries a signature (*.sigstore.json) and SLSA provenance
# (*.intoto.jsonl) next to it.
# assets*, not the OCI registry. Sign and attest BOTH installer files
# directly so the release carries a signature (*.sigstore.json) and SLSA
# provenance (*.intoto.jsonl) next to each. The installer ships as two
# files — crds.yaml is applied first, then install.yaml (see the release
# notes); each is signed independently so either can be verified alone.
- name: Sign install.yaml (cosign keyless)
run: cosign sign-blob --bundle install.yaml.sigstore.json --yes dist/install.yaml

- name: Sign crds.yaml (cosign keyless)
run: cosign sign-blob --bundle crds.yaml.sigstore.json --yes dist/crds.yaml

- name: Attest SLSA provenance for install.yaml
id: attest-install-yaml
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
Expand All @@ -329,7 +337,16 @@ jobs:
- name: Rename provenance bundle to the Scorecard-recognized suffix
run: cp "${{ steps.attest-install-yaml.outputs.bundle-path }}" install.yaml.intoto.jsonl

- name: Upload install.yaml as release asset
- name: Attest SLSA provenance for crds.yaml
id: attest-crds-yaml
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: dist/crds.yaml

- name: Rename crds provenance bundle to the Scorecard-recognized suffix
run: cp "${{ steps.attest-crds-yaml.outputs.bundle-path }}" crds.yaml.intoto.jsonl

- name: Upload installer bundle as release assets
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: ${{ needs.release-please.outputs.tag_name }}
Expand All @@ -338,6 +355,9 @@ jobs:
# uploads).
draft: true
files: |
dist/crds.yaml
crds.yaml.sigstore.json
crds.yaml.intoto.jsonl
dist/install.yaml
install.yaml.sigstore.json
install.yaml.intoto.jsonl
Expand Down
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ __debug*
charts/gitops-reverser/crds/*.yaml
charts/gitops-reverser/config/*.yaml

# Generated single-file installer artifact (not tracked).
/dist/install.yaml
# Generated installer artifacts (not tracked). `task dist-install` emits the split bundle
# — crds.yaml (applied first) + install.yaml — and CI regenerates both before packaging a
# release. Ignore the whole directory so a new artifact never lands in a commit by accident.
/dist/

*.o.yaml
*.ignore.*
Expand Down
8 changes: 8 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,12 @@ resources:
kind: ClusterWatchRule
path: github.com/ConfigButler/gitops-reverser/api/v1alpha3
version: v1alpha3
- api:
crdVersion: v1
namespaced: false
controller: true
domain: configbutler.ai
kind: ClusterProvider
path: github.com/ConfigButler/gitops-reverser/api/v1alpha3
version: v1alpha3
version: "3"
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ Production use should follow an environment-specific review. Deferred directions
This brings up the **demo**: a starter `GitProvider`, `GitTarget`, and `WatchRule` in a
`gitops-reverser-quickstart-demo` namespace. It watches ConfigMaps in that namespace and writes them
to `<your-repo>/live-cluster` on the `main` branch. It runs in **`configured-author` mode** (one
committer identity, no Redis) by default.
committer identity, no Redis) by default. The chart also renders the cluster-scoped `default`
`ClusterProvider`, so the starter target's omitted source reference resolves to the operator's own
cluster.

![Config basics diagram showing the relationship between GitProvider, GitTarget, and WatchRule](docs/images/config-basics.excalidraw.svg)

Expand Down Expand Up @@ -164,9 +166,9 @@ credentials Secrets are accepted as-is (they must have **write** access). See
[`docs/github-setup-guide.md`](docs/github-setup-guide.md) for the full GitHub guide and HTTPS/PAT
fallback.

**4. Install GitOps Reverser with the demo**
**4. Install GitOps Reverser with the demo enabled**

A single install enables the demo and points the starter `GitProvider` at your repo:
Point the starter `GitProvider` at your repo and install:

```bash
helm install gitops-reverser \
Expand Down
14 changes: 14 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ model, and operational risks for your environment.
For what the controller can access and which pieces are sensitive, see
[`docs/security-model.md`](docs/security-model.md).

## Shared audit-ingress trust model

Audit ingress uses mutual TLS, but the current multi-cluster routes use the client certificate to
authenticate membership in the shared audit CA—not to bind a sender to one `ClusterProvider`. A
source that holds the shared client credential can therefore submit audit facts for any configured
provider, whether the provider is selected by `/audit-webhook/<provider>` or by shared-stream
annotation routing.

This is an explicit, accepted operating assumption for now: use shared ingress only when a highly
privileged control plane manages all participating source clusters, protects the shared credential,
and treats those sources as mutually trusted. It is not a tenant-isolation boundary. Deploy separate
instances or keep attribution disabled when one source must not be able to influence another source's
Git author history. Provider-bound client identities may be added if that stronger boundary is needed.

## Reporting a vulnerability

Please do not open a public GitHub issue for security-sensitive reports.
Expand Down
14 changes: 12 additions & 2 deletions Taskfile-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ tasks:
IMG: '{{.DEV_IMG}}'

dist-install:
desc: Generate consolidated YAML from the Helm chart
desc: Generate the split installer bundle (dist/crds.yaml + dist/install.yaml) from the Helm chart
deps:
- helm-sync
sources:
Expand All @@ -409,24 +409,34 @@ tasks:
- charts/gitops-reverser/Chart.yaml
- charts/gitops-reverser/values.yaml
- charts/gitops-reverser/templates/**
- charts/gitops-reverser/crds/*.yaml
- exclude: api/**/*_test.go
- exclude: internal/**/*_test.go
- exclude: cmd/**/*_test.go
- exclude: api/**/zz_generated.deepcopy.go
- exclude: internal/**/zz_generated.deepcopy.go
- exclude: cmd/**/zz_generated.deepcopy.go
generates:
- dist/crds.yaml
- dist/install.yaml
cmds:
- |
mkdir -p dist
# CRDs ship as their OWN file, applied FIRST. The bundle contains the reserved `default`
# ClusterProvider — a custom resource that cannot be applied in the same `kubectl apply` as
# the CRD defining it: kubectl builds its RESTMapper up front and fails with
# `no matches for kind "ClusterProvider"`. So: kubectl apply -f dist/crds.yaml, then
# kubectl apply -f dist/install.yaml. `helm install` is unaffected — it installs
# charts/gitops-reverser/crds/ in its own first phase, which is why --include-crds is gone
# here. Each chart CRD file already begins with its own `---`, so a plain cat is valid
# multi-doc YAML.
cat charts/gitops-reverser/crds/*.yaml > dist/crds.yaml
{{.HELM}} template {{.INSTALL_NAME}} charts/gitops-reverser \
--namespace {{.NAMESPACE}} \
--set labels.managedBy=kubectl \
--set createNamespace=true \
--set queue.redis.addr={{.DEFAULT_AUDIT_REDIS_ADDR}} \
--set queue.redis.auth.existingSecret=valkey-auth \
--include-crds \
> dist/install.yaml

clean:
Expand Down
Loading