Skip to content

Commit d240120

Browse files
authored
Merge pull request #55 from rapidfort/52-add-buildah-opt
Add --buildah-opt
2 parents df541bd + 5fe14b2 commit d240120

File tree

8 files changed

+166
-6
lines changed

8 files changed

+166
-6
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- Added `--buildah-opt` to pass arguments directly to Buildah
1112
- Added `--export-cache` and `--import-cache` flags for BuildKit advanced caching.
13+
1214
### Changed
1315

1416
### Fixed
1517
- Temporary build directories are now cleaned up on failed builds
1618
- fixed bug where digest file was not being created when --no-push is set
19+
1720
### Removed
1821

1922
## [1.0.23] - 2026-02-18

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ Kimia supports a comprehensive set of command-line arguments. Key options includ
299299
| Argument | Description |
300300
|----------|-------------|
301301
| `--buildkit-opt` | Pass options directly to BuildKit |
302+
| `--buildah-opt` | Pass additional flags to Buildah |
302303

303304
**Full reference:** See [CLI Reference](docs/cli-reference.md) for complete documentation.
304305

docs/cli-reference.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,19 @@ kimia --context=. \
507507
--buildkit-opt=platform=linux/amd64,linux/arm64
508508
```
509509

510+
### Buildah Options
511+
512+
| Argument | Description | Example |
513+
|----------|-------------|---------|
514+
| `--buildah-opt` | Pass options directly to Buildah | `--buildah-opt "--squash"` |
515+
516+
517+
```bash
518+
# Squash all layers into one
519+
kimia --context=. \
520+
--destination=registry.io/myapp:latest \
521+
--buildah-opt "--squash"
522+
510523
### Storage Driver
511524
512525
Kimia supports two storage drivers:

src/cmd/kimia/args.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func parseArgs(args []string) *Config {
2323
ImportCache: []string{}, // BuildKit --import-cache options
2424
CosignKeyPath: "/etc/cosign/cosign.key",
2525
CosignPasswordEnv: "COSIGN_PASSWORD",
26+
BuildahOpts: []string{}, // Direct Buildah bud options
2627
}
2728

2829
// If no arguments provided, show help
@@ -384,6 +385,18 @@ func parseArgs(args []string) *Config {
384385
logger.Fatal("--cosign-password-env requires a value")
385386
}
386387

388+
case "--buildah-opt":
389+
var optStr string
390+
if value != "" {
391+
optStr = value
392+
} else if i+1 < len(args) { // no HasPrefix guard — value may start with -
393+
i++
394+
optStr = args[i]
395+
} else {
396+
logger.Fatal("--buildah-opt requires a value")
397+
}
398+
config.BuildahOpts = append(config.BuildahOpts, optStr)
399+
387400
default:
388401
if !strings.HasPrefix(arg, "-") {
389402
logger.Warning("Unexpected argument: %s", arg)

src/cmd/kimia/config.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,14 @@ type Config struct {
6666

6767
// Level 3: Direct BuildKit options (escape hatch)
6868
BuildKitOpts []string // Raw --opt values to pass to buildctl
69-
69+
7070
// Signing
7171
Sign bool // Enable cosign signing
7272
CosignKeyPath string // Path to cosign private key
7373
CosignPasswordEnv string // Environment variable for cosign password
74+
75+
// Direct Buildah options
76+
BuildahOpts []string // Raw --opt values to pass to buildah bud
7477
}
7578

7679
// AttestationConfig represents a single --attest flag

src/cmd/kimia/help.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ func printHelp() {
3333
fmt.Println(" --no-push Build only, skip push")
3434
fmt.Println(" --cache Enable layer caching")
3535
fmt.Println(" --cache-dir PATH Cache directory path")
36+
if build.DetectBuilder() == "buildah" {
37+
fmt.Println("BUILDAH OPTIONS:")
38+
fmt.Println(" --buildah-opt \"FLAG [VALUE]\" Pass additional flags to buildah bud (Buildah only, repeatable)")
39+
fmt.Println(" Values cannot contain shell metacharacters")
40+
fmt.Println(" (;, &, |, etc.).")
41+
fmt.Println(" Example: --buildah-opt \"--squash\"")
42+
fmt.Println()
43+
}
3644
if build.DetectBuilder() == "buildkit" {
3745
fmt.Println(" --export-cache SPEC Export build cache (BuildKit only, repeatable)")
3846
fmt.Println(" Examples:")

src/cmd/kimia/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ func run(config *Config, builder string) error {
226226
Sign: config.Sign,
227227
CosignKeyPath: config.CosignKeyPath,
228228
CosignPasswordEnv: config.CosignPasswordEnv,
229+
BuildahOpts: config.BuildahOpts,
229230
}
230231

231232
// Execute build

src/internal/build/builder.go

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ type Config struct {
7171
Sign bool // Enable signing with cosign
7272
CosignKeyPath string // Path to cosign private key
7373
CosignPasswordEnv string // Environment variable for cosign password
74+
75+
// Direct Buildah options
76+
BuildahOpts []string
7477
}
7578

7679
// AttestationConfig represents a single --attest flag
@@ -125,6 +128,11 @@ func executeBuildah(config Config, ctx *Context) error {
125128
logger.Debug("Running as non-root (UID %d) - using chroot isolation with user namespaces", os.Getuid())
126129
}
127130

131+
// Warn if --buildkit-opt was passed — these are ignored by Buildah
132+
if len(config.BuildKitOpts) > 0 {
133+
logger.Warning("--buildkit-opt flags are ignored when using Buildah backend: %v", config.BuildKitOpts)
134+
}
135+
128136
logger.Info("Starting buildah build...")
129137

130138
// ========================================
@@ -262,13 +270,35 @@ func executeBuildah(config Config, ctx *Context) error {
262270
args = append(args, "-t", dest)
263271
}
264272

273+
// ========================================
274+
// Pass-through args — must be added before ctx.Path
275+
// ========================================
276+
// Supports both "--flag=value" and "--flag value" (e.g. Buildah's --sbom flags).
277+
// Each --buildah-opt is split into at most two tokens; validateBuildahInputs
278+
// enforces that the flag name portion contains no dangerous characters.
279+
for _, opt := range config.BuildahOpts {
280+
parts := strings.SplitN(opt, " ", 2)
281+
if len(parts) == 2 {
282+
args = append(args, parts[0], parts[1])
283+
} else {
284+
args = append(args, parts[0])
285+
}
286+
}
287+
265288
// Add context path
266289
args = append(args, ctx.Path)
267290

268291
// Log the command
269-
logger.Debug("Buildah command: buildah %s", strings.Join(args, " "))
292+
logger.Debug("Buildah command: buildah %s", strings.Join(sanitizeCommandArgs(args), " "))
270293

271294
// Execute buildah
295+
// #nosec G204 -- all args validated by validateBuildahInputs:
296+
// - BuildahOpts: flag name validated by ValidateBuildctlArg (null bytes,
297+
// shell metacharacters); value portion checked for null bytes only since
298+
// scanner commands and paths may contain characters the general validator
299+
// would reject; conflict-checked against Kimia-managed flags
300+
// - All other args (dockerfile, build-arg, label, dest) are Kimia-constructed
301+
// from validated inputs
272302
cmd := exec.Command("buildah", args...)
273303
var stdoutBuf, stderrBuf bytes.Buffer
274304
cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
@@ -482,18 +512,97 @@ func validateBuildahInputs(config Config, ctx *Context) error {
482512
homeDir = "/home/kimia"
483513
}
484514
homeDir = filepath.Clean(homeDir)
485-
515+
486516
if err := validation.ValidatePathWithinBase(config.TarPath, homeDir); err != nil {
487517
return fmt.Errorf("invalid tar path: %v", err)
488518
}
489519
}
490520

521+
// Flags already managed explicitly by Kimia.
522+
// IMPORTANT: If new flags are added to executeBuildah, add them here too.
523+
conflictingFlags := map[string]string{
524+
"-f": "use -f/--dockerfile instead",
525+
"--file": "use -f/--dockerfile instead",
526+
"--build-arg": "use --build-arg instead",
527+
"--label": "use --label instead",
528+
"--target": "use -t/--target instead",
529+
"--platform": "use --custom-platform instead",
530+
"--timestamp": "use --timestamp or --reproducible instead",
531+
"--source-date-epoch": "use --timestamp or --reproducible instead",
532+
// Don't prevent users from overriding --tls-verify
533+
//"--tls-verify": "use --insecure or --insecure-registry instead",
534+
"--retry": "use --image-download-retry instead",
535+
"-t": "use -d/--destination instead",
536+
"--tag": "use -d/--destination instead",
537+
"--no-cache": "use --cache=false instead",
538+
"--layers": "use --cache instead",
539+
// Security-sensitive flags managed implicitly by Kimia via BUILDAH_ISOLATION=chroot
540+
"--isolation": "isolation is managed by Kimia (chroot)",
541+
"--userns": "user namespace configuration is managed by Kimia",
542+
"--userns-uid-map": "user namespace configuration is managed by Kimia",
543+
"--userns-gid-map": "user namespace configuration is managed by Kimia",
544+
"--cap-add": "capability management is outside Kimia's scope",
545+
"--cap-drop": "capability management is outside Kimia's scope",
546+
"--security-opt": "security options are managed by Kimia",
547+
"--privileged": "privileged mode is not supported by Kimia",
548+
}
549+
550+
for i, opt := range config.BuildahOpts {
551+
opt = strings.TrimSpace(opt)
552+
553+
if opt == "" {
554+
return fmt.Errorf("--buildah-opt value %d is empty", i)
555+
}
556+
557+
// Split into at most two tokens: "--flag" and optional "value".
558+
// This matches the split logic in executeBuildah and correctly handles
559+
// both "--flag=value" and "--flag value" (e.g. Buildah's --sbom flags).
560+
parts := strings.SplitN(opt, " ", 2)
561+
flagName := parts[0]
562+
if idx := strings.Index(flagName, "="); idx != -1 {
563+
flagName = flagName[:idx]
564+
}
565+
566+
// Validate the flag name only — not the full opt string.
567+
// The old ValidateBuildctlArg(opt) call blocked spaces, which breaks
568+
// legitimate "--flag value" opts like "--sbom syft-cyclonedx".
569+
if err := validation.ValidateBuildctlArg(flagName); err != nil {
570+
return fmt.Errorf("invalid --buildah-opt flag name %d (%q): %v", i, flagName, err)
571+
}
572+
573+
// Validate the value portion for null bytes regardless of form.
574+
// Space-separated values (parts[1]) are passed through as-is since
575+
// they may be paths or scanner commands that ValidateBuildctlArg
576+
// would incorrectly reject.
577+
var optValue string
578+
if len(parts) == 2 {
579+
optValue = parts[1] // "--flag value" form
580+
} else if idx := strings.Index(parts[0], "="); idx != -1 {
581+
optValue = parts[0][idx+1:] // "--flag=value" form
582+
}
583+
if strings.Contains(optValue, "\x00") {
584+
return fmt.Errorf("--buildah-opt value %d contains null byte", i)
585+
}
586+
587+
if suggestion, conflicts := conflictingFlags[flagName]; conflicts {
588+
return fmt.Errorf(
589+
"--buildah-opt %q is managed by Kimia: %s",
590+
flagName, suggestion,
591+
)
592+
}
593+
}
594+
491595
return nil
492596
}
493597

494598
func executeBuildKit(config Config, ctx *Context) error {
495599
logger.Info("Starting BuildKit build...")
496600

601+
// Warn if --buildah-opt was passed — these are ignored by BuildKit
602+
if len(config.BuildahOpts) > 0 {
603+
logger.Warning("--buildah-opt flags are ignored when using BuildKit backend: %v", config.BuildahOpts)
604+
}
605+
497606
// ========================================
498607
// SETUP: Environment and paths
499608
// ========================================
@@ -1720,7 +1829,7 @@ func sanitizeCommandArgs(args []string) []string {
17201829
"SECRET",
17211830
"CREDENTIALS",
17221831
}
1723-
1832+
17241833
sanitized := make([]string, len(args))
17251834
for i, arg := range args {
17261835
if strings.HasPrefix(arg, "context=") || strings.HasPrefix(arg, "dockerfile=") {
@@ -1753,8 +1862,17 @@ func sanitizeCommandArgs(args []string) []string {
17531862
sanitized[i] = arg
17541863
}
17551864
} else {
1756-
// For any other arg that might be a URL
1757-
sanitized[i] = logger.SanitizeGitURL(arg)
1865+
// Only sanitize args that look like URLs -- calling SanitizeGitURL
1866+
// on non-URL values (e.g. --buildah-opt scanner commands) causes
1867+
// spaces and braces to be URL-encoded in log output.
1868+
if strings.HasPrefix(arg, "http://") ||
1869+
strings.HasPrefix(arg, "https://") ||
1870+
strings.HasPrefix(arg, "git://") ||
1871+
strings.HasPrefix(arg, "git@") {
1872+
sanitized[i] = logger.SanitizeGitURL(arg)
1873+
} else {
1874+
sanitized[i] = arg
1875+
}
17581876
}
17591877
}
17601878
return sanitized

0 commit comments

Comments
 (0)