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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ jobs:
image: plumber:ci
fail-build: true
severity-cutoff: high
only-fixed: true

# trivy:
# name: Container Scan (Trivy)
Expand Down
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build stage
FROM golang:1.25-alpine@sha256:8e02eb337d9e0ea459e041f1ee5eece41cbb61f1d83e7d883a3e2fb4862063fa AS builder
FROM golang:1.26-alpine@sha256:c2a1f7b2095d046ae14b286b18413a05bb82c9bca9b25fe7ff5efef0f0826166 AS builder

# Set working directory
WORKDIR /app
Expand All @@ -20,10 +20,10 @@ RUN cp .plumber.yaml internal/defaultconfig/default.yaml
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o plumber .

# Final stage - Alpine (small, has shell for CI compatibility)
FROM alpine:3.21@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
FROM alpine:3.22@sha256:55ae5d250caebc548793f321534bc6a8ef1d116f334f18f4ada1b2daad3251b2

# Install CA certificates for HTTPS API calls
RUN apk --no-cache add ca-certificates
# Upgrade base packages (including OpenSSL) and install CA certificates
RUN apk --no-cache upgrade && apk --no-cache add ca-certificates

# Copy binary from builder
COPY --from=builder /app/plumber /plumber
Expand Down
105 changes: 96 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ Choose **one** of these methods. You don't need both:
- [Project Badges](#project-badges)
- [Installation](#-installation)
- [CLI Reference](#-cli-reference)
- [`plumber config init`](#plumber-config-init)
- [`plumber explain`](#plumber-explain)
- [Self-Hosted GitLab](#%EF%B8%8F-self-hosted-gitlab)
- [Troubleshooting](#-troubleshooting)
- [See it in action](#-see-it-in-action)
Expand Down Expand Up @@ -125,12 +127,22 @@ chmod +x plumber-* && sudo mv plumber-* /usr/local/bin/plumber

> 📦 See [Installation](#-installation) for Windows, Docker, or building from source.

### Step 2: Generate a Config File
### Step 2: Create a Config File

**Interactive minimal config** (recommended for first-time setup):

```bash
plumber config init
```

**Or** generate the full commented template:

```bash
plumber config generate
```

In non-interactive environments (for example CI), use `plumber config generate` to write the default template with comments, then trim or adjust as needed.

This creates `.plumber.yaml` with [default](./.plumber.yaml) compliance rules. You can customize it later.

### Step 3: Create & Set Your Token
Expand Down Expand Up @@ -879,10 +891,10 @@ brew install plumber
To install a specific version:

```bash
brew install getplumber/plumber/plumber@0.1.77
brew install getplumber/plumber/plumber@0.1.82
```

> **Note:** Versioned formulas are keg-only. Use the full path for example `/usr/local/opt/plumber@0.1.77/bin/plumber` or run `brew link plumber@0.1.77` to add it to your PATH.
> **Note:** Versioned formulas are keg-only. Use the full path for example `/usr/local/opt/plumber@0.1.82/bin/plumber` or run `brew link plumber@0.1.82` to add it to your PATH.

### Mise

Expand Down Expand Up @@ -1062,9 +1074,31 @@ export PLUMBER_NO_UPDATE_CHECK=1
| `1` | Compliance failure (compliance < threshold) |
| `2` | Runtime error (config error, network failure, missing token, etc.) |

### `plumber config init`

Interactive wizard to create a **minimal** `.plumber.yaml` by choosing policy areas (images, pipeline composition, branch protection, variables). Omits controls you do not select. For every control in a selected area, the wizard asks for the fields defined in the schema (for example forbidden include refs, security job patterns and sub-checks, trusted script URLs, job variable lists, DinD options, branch protection levels, debug and unsafe-expansion variable lists, regex allowlists, and required components or templates via the `required` expression).

Requires an interactive terminal. For the full default template with inline comments (including in CI), use [`plumber config generate`](#plumber-config-generate).

```bash
plumber config init [flags]
```

| Flag | Default | Description |
|------|---------|-------------|
| `--output`, `-o` | `.plumber.yaml` | Output file path |
| `--force`, `-f` | `false` | Overwrite existing file without asking |

**Examples:**

```bash
plumber config init
plumber config init --output ./configs/plumber.yaml
```

### `plumber config generate`

Generate a default `.plumber.yaml` configuration file.
Writes the **official default** `.plumber.yaml`: the full template Plumber ships with, including comments and every control documented inline. Use [`plumber config init`](#plumber-config-init) instead if you want a smaller, wizard-driven file with only the checks you pick.

```bash
plumber config generate [flags]
Expand All @@ -1078,13 +1112,8 @@ plumber config generate [flags]
**Examples:**

```bash
# Generate default config
plumber config generate

# Custom filename
plumber config generate --output my-plumber.yaml

# Overwrite existing conf file
plumber config generate --force
```

Expand Down Expand Up @@ -1204,6 +1233,64 @@ Unknown keys in your config (not in defaults):
controls.containerImageMustNotUseForbiddenTag ← possible typo? Did you mean "controls.containerImageMustNotUseForbiddenTags.enabled"?
```

### `plumber explain`

Look up detailed information about a Plumber issue code directly in the terminal. Useful for understanding what an issue means, why it matters, and how to fix it — without leaving your current context.

```bash
plumber explain [ISSUE-CODE] [flags]
```

`ISSUE-CODE` supports both `ISSUE-412` and shorthand numeric form like `412`.

| Flag | Default | Description |
|------|---------|-------------|
| `--list` | `false` | List all issue codes with short descriptions |
| `--all` | `false` | Show detailed information for all issue codes |
| `--json` | `false` | Output in JSON format |

**Examples:**

```bash
# Explain a specific issue code
plumber explain ISSUE-412

# Shorthand form (equivalent)
plumber explain 412

# List all available issue codes
plumber explain --list

# Get machine-readable output
plumber explain --json ISSUE-412

# Full reference dump
plumber explain --all

# Full reference in JSON
plumber explain --all --json
```

**Sample output:**

```
ISSUE-412: Docker-in-Docker service detected
Control: pipelineMustNotUseDockerInDocker

Description:
A CI/CD job uses a Docker-in-Docker (dind) service. On shared runners
running in privileged mode, this enables container escape, lateral
movement, and access to secrets from other jobs on the same runner.

Remediation:
Replace Docker-in-Docker with a safer alternative such as Kaniko or
Buildah for building container images. These tools do not require
privileged mode and avoid the security risks of running a Docker
daemon inside a CI container.

Documentation: https://getplumber.io/docs/use-plumber/issues/ISSUE-412
```

---

## ⚠️ Self-Hosted GitLab
Expand Down
2 changes: 1 addition & 1 deletion cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func runAnalyze(cmd *cobra.Command, args []string) error {
plumberConfig, configPath, configWarnings, err := configuration.LoadPlumberConfig(configFile)
if err != nil {
if strings.Contains(err.Error(), "config file not found") {
return fmt.Errorf("configuration file not found: %w. You can generate a default config with `plumber config generate`", err)
return fmt.Errorf("configuration file not found: %w. Create one with `plumber config generate` or `plumber config init`", err)
}
return fmt.Errorf("configuration error: %w", err)
}
Expand Down
46 changes: 30 additions & 16 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,31 +84,37 @@ Examples:

var configGenerateCmd = &cobra.Command{
Use: "generate",
Short: "Generate a default .plumber.yaml configuration file",
Long: `Generate a default .plumber.yaml configuration file.
Short: "Write the default .plumber.yaml template (commented)",
Long: `Write the official default .plumber.yaml to disk: the same file shipped
with Plumber, including comments and every control documented inline.

This creates a configuration file with sensible defaults that you can
customize for your organization's compliance requirements.

The generated config includes:
- Container image tag policies (forbid 'latest', 'dev', etc.)
- Container image digest pinning policy
- Trusted registry whitelist
- Branch protection requirements
Use this when you want the full reference template (for example the first
commit in a repo, or CI that copies a known baseline).

Examples:
# Generate default config in current directory
plumber config generate

# Generate config with custom filename
plumber config generate --output my-plumber-config.yaml

# Overwrite existing file
plumber config generate --output my-plumber.yaml
plumber config generate --force
`,
RunE: runConfigGenerate,
}

var configInitCmd = &cobra.Command{
Use: "init",
Short: "Interactive wizard for a tailored minimal .plumber.yaml",
Long: `Walk through policy choices in the terminal and write a minimal .plumber.yaml:
only the controls you enable, compact YAML, no long commented tutorial.

Use generate for the full default template with comments. Use init when you
want a smaller file shaped to your project.

Requires an interactive terminal (TTY). In CI or headless environments, use
generate and edit the file, or commit a hand-tuned config.

Related: https://github.com/getplumber/plumber/issues/125`,
RunE: runConfigInit,
}

var configDiffCmd = &cobra.Command{
Use: "diff",
Short: "Display the difference between current config and defaults",
Expand Down Expand Up @@ -136,6 +142,7 @@ func init() {
rootCmd.AddCommand(configCmd)
configCmd.AddCommand(configViewCmd)
configCmd.AddCommand(configGenerateCmd)
configCmd.AddCommand(configInitCmd)
configCmd.AddCommand(configValidateCmd)
configCmd.AddCommand(configDiffCmd)

Expand All @@ -151,6 +158,9 @@ func init() {
configGenerateCmd.Flags().StringVarP(&configGenerateOutput, "output", "o", ".plumber.yaml", "Output file path")
configGenerateCmd.Flags().BoolVarP(&configGenerateForce, "force", "f", false, "Overwrite existing file")

configInitCmd.Flags().StringVarP(&configInitOutput, "output", "o", ".plumber.yaml", "Path to write the generated configuration")
configInitCmd.Flags().BoolVarP(&configInitForce, "force", "f", false, "Overwrite existing file without asking")

// config diff flags
configDiffCmd.Flags().StringVarP(&configDiffFile, "config", "c", ".plumber.yaml", "Path to configuration file")
configDiffCmd.Flags().BoolVar(&configDiffNoColor, "no-color", false, "Disable colorized output")
Expand Down Expand Up @@ -322,6 +332,10 @@ func formatNestedArrays(input string) string {
}

func runConfigGenerate(cmd *cobra.Command, args []string) error {
if !verbose {
logrus.SetLevel(logrus.WarnLevel)
}

// Check if file already exists
if _, err := os.Stat(configGenerateOutput); err == nil {
if !configGenerateForce {
Expand Down
Loading
Loading