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
162 changes: 162 additions & 0 deletions COSIGN_USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Cosign Integration for Drone-Docker

This document describes how to use the cosign container image signing feature in drone-docker.

## Overview

The drone-docker plugin now supports automatic container image signing using cosign after each successful push. This provides cryptographic verification that images haven't been tampered with.

## Environment Variables

The plugin accepts three cosign-related environment variables:

### `PLUGIN_COSIGN_PRIVATE_KEY` (Required for signing)
- **Description**: Private key for signing (PEM format content or file path)
- **Format**: Either PEM content or file path to private key
- **Usage**: Should be provided via secrets

### `PLUGIN_COSIGN_PASSWORD` (Optional)
- **Description**: Password for encrypted private keys
- **Usage**: Only needed if your private key is password-protected

### `PLUGIN_COSIGN_PARAMS` (Optional)
- **Description**: Additional cosign parameters
- **Examples**:
- `-a build_id=123` (add annotations)
- `--tlog-upload=false` (disable transparency log)
- `--rekor-url=https://custom-rekor.example.com` (custom rekor instance)

## Usage Examples

### 1. Basic Signing (Drone)

```yaml
kind: pipeline
type: docker
name: default

steps:
- name: docker
image: plugins/docker
settings:
repo: myregistry/myapp
tags: latest
cosign_private_key:
from_secret: cosign_private_key
cosign_password:
from_secret: cosign_password
```

### 2. Advanced Signing with Annotations (Drone)

```yaml
steps:
- name: docker
image: plugins/docker
settings:
repo: myregistry/myapp
tags:
- latest
- ${DRONE_BUILD_NUMBER}
cosign_private_key:
from_secret: cosign_private_key
cosign_params: "-a build_id=${DRONE_BUILD_NUMBER} -a commit_sha=${DRONE_COMMIT_SHA} -a branch=${DRONE_BRANCH}"
```

### 3. Harness CI/CD Usage

```yaml
- step:
type: Plugin
name: Build and Sign
identifier: build_and_sign
spec:
connectorRef: account.harnessImage
image: plugins/docker
settings:
repo: myregistry/myapp
tags: <+pipeline.sequenceId>
cosign_private_key: <+secrets.getValue("cosign_private_key")>
cosign_password: <+secrets.getValue("cosign_password")>
cosign_params: "-a harness_build=<+pipeline.sequenceId> -a harness_project=<+project.name>"
```

## Key Management

### Generating Cosign Keys

```bash
# Generate a new key pair
cosign generate-key-pair

# This creates:
# - cosign.key (private key)
# - cosign.pub (public key)
```

### Storing Keys Securely
**Harness Secrets:**
1. Go to Project Settings → Secrets
2. Create new secret with type "File" for private key
3. Create new secret with type "Text" for password

## Security Features

### Automatic Validation
- ✅ **Private key format validation**: Ensures PEM format is correct
- ✅ **Password requirement detection**: Warns if encrypted key needs password
- ✅ **Keyless signing prevention**: Warns that OIDC keyless signing isn't supported

### Error Handling
- **Invalid private key**: `❌ Invalid private key format. Expected PEM format`
- **Missing password**: `🔐 Encrypted private key requires password. Set PLUGIN_COSIGN_PASSWORD`
- **Keyless signing**: `⚠️ WARNING: Keyless signing (OIDC) isn't supported yet in this plugin`

## Signing Behavior

### When Signing Occurs
- ✅ **After each successful push**: Images are signed immediately after push
- ✅ **Multiple tags**: Each tag gets signed individually
- ✅ **Push-only mode**: Works with existing images
- ✅ **Dry-run respect**: Skips signing in dry-run mode

### Image References
- **Preferred**: Signs by digest (e.g., `image@sha256:abc123...`) for security
- **Fallback**: Signs by tag if digest unavailable

### Authentication
- **Registry auth**: Automatically uses existing Docker registry credentials

## Verification

To verify a signed image:

```bash
# Verify with public key
cosign verify --key cosign.pub myregistry/myapp:latest

# Verify with annotations
cosign verify --key cosign.pub \
-a build_id=123 \
myregistry/myapp:latest
```

## Troubleshooting

### Common Issues

1. **"cosign: command not found"**
- The container image includes cosign binary
- Use the latest plugin image: `plugins/docker:latest`

2. **"keyless signing not supported"**
- This plugin only supports private key signing
- Don't use `--oidc` or `--identity-token` in `cosign_params`

3. **"encrypted private key requires password"**
- Set `PLUGIN_COSIGN_PASSWORD` environment variable
- Or use an unencrypted private key

4. **Registry authentication issues**
- Cosign uses the same Docker registry credentials
- Ensure Docker login is working first
21 changes: 21 additions & 0 deletions cmd/drone-docker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,22 @@ func main() {
Usage: "access token",
EnvVar: "ACCESS_TOKEN",
},
// Cosign signing configuration
cli.StringFlag{
Name: "cosign.private-key",
Usage: "cosign private key content or file path for signing",
EnvVar: "PLUGIN_COSIGN_PRIVATE_KEY",
},
cli.StringFlag{
Name: "cosign.password",
Usage: "password for encrypted cosign private key",
EnvVar: "PLUGIN_COSIGN_PASSWORD",
},
cli.StringFlag{
Name: "cosign.params",
Usage: "additional cosign parameters (e.g., annotations, flags)",
EnvVar: "PLUGIN_COSIGN_PARAMS",
},
}

if err := app.Run(os.Args); err != nil {
Expand Down Expand Up @@ -398,6 +414,11 @@ func run(c *cli.Context) error {
BaseImageRegistry: c.String("docker.baseimageregistry"),
BaseImageUsername: c.String("docker.baseimageusername"),
BaseImagePassword: c.String("docker.baseimagepassword"),
Cosign: docker.CosignConfig{
PrivateKey: c.String("cosign.private-key"),
Password: c.String("cosign.password"),
Params: c.String("cosign.params"),
},
}

if c.Bool("tags.auto") {
Expand Down
1 change: 1 addition & 0 deletions daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
const dockerExe = "/usr/local/bin/docker"
const dockerdExe = "/usr/local/bin/dockerd"
const dockerHome = "/root/.docker/"
const cosignExe = "/usr/local/bin/cosign"

func (p Plugin) startDaemon() {
cmd := commandDaemon(p.Daemon)
Expand Down
2 changes: 2 additions & 0 deletions daemon_win.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//go:build windows
// +build windows

package docker

const dockerExe = "C:\\bin\\docker.exe"
const dockerdExe = ""
const dockerHome = "C:\\ProgramData\\docker\\"
const cosignExe = "C:\\bin\\cosign.exe"

func (p Plugin) startDaemon() {
// this is a no-op on windows
Expand Down
Loading