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
51 changes: 51 additions & 0 deletions .github/workflows/release_inject.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Release inject

on:
workflow_dispatch:
push:
tags:
- "inject/v[0-9]+.[0-9]+.[0-9]+*"

permissions:
contents: write
packages: write

concurrency: release-inject

jobs:
release:
runs-on: depot-ubuntu-24.04-4
steps:
- name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0

- name: Setup Go
uses: ./.github/actions/setup-go
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3

- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF#refs/tags/inject/}" >> $GITHUB_OUTPUT

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
distribution: goreleaser-pro
version: "~> v2"
args: release --clean --config cmd/inject/.goreleaser.yaml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ go.work
go.work.sum

/unkey
/unkey-env
/inject
# Added by goreleaser init:
dist/
10 changes: 0 additions & 10 deletions Dockerfile.unkey-env

This file was deleted.

26 changes: 13 additions & 13 deletions Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ debug_mode = cfg.get('debug', False)
print("Tilt starting with services: %s" % services)

# Suppress warnings for images used indirectly (injected into pods by webhook)
update_settings(suppress_unused_image_warnings=["unkey-env:latest"])
update_settings(suppress_unused_image_warnings=["inject:latest"])

# Create namespace using the extension with allow_duplicates
namespace_create('unkey', allow_duplicates=True)
Expand All @@ -42,7 +42,6 @@ start_preflight = 'all' in services or 'preflight' in services
# Apply RBAC
k8s_yaml('k8s/manifests/rbac.yaml')


# Redis service
redis_started = False
if start_api: # Redis is needed by API service
Expand Down Expand Up @@ -154,9 +153,10 @@ if start_api or start_ctrl or start_krane or start_preflight:
print("Building Unkey binary...")
# Build locally first for faster updates
local_resource(
'unkey-compile',
'build-unkey',
'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/unkey ./main.go',
deps=['./main.go', './pkg', './cmd', './svc'],
labels=['build'],
)


Expand Down Expand Up @@ -185,7 +185,7 @@ if start_api:
if redis_started: api_deps.append('redis')

# Add compilation dependency for Unkey services
api_deps.append('unkey-compile')
api_deps.append('build-unkey')

k8s_resource(
'api',
Expand Down Expand Up @@ -220,7 +220,7 @@ if start_ctrl:
if start_s3: ctrl_deps.append('s3')
if start_restate: ctrl_deps.append('restate')
# Add compilation dependency for Unkey services
ctrl_deps.append('unkey-compile')
ctrl_deps.append('build-unkey')

k8s_resource(
'ctrl',
Expand Down Expand Up @@ -252,7 +252,7 @@ if start_krane:
# Build dependency list
krane_deps = []
# Add compilation dependency for Unkey services
krane_deps.append('unkey-compile')
krane_deps.append('build-unkey')
krane_deps.append('ctrl')

k8s_resource(
Expand Down Expand Up @@ -282,13 +282,13 @@ if start_preflight:
labels=['unkey'],
)

# Build unkey-env image for injection into customer pods
# Uses local_resource so it shows up in Tilt UI and can be triggered
# Build inject image for injection into customer pods
# Uses local_resource so it shows up in Tilt UI and can be triggered manually
local_resource(
'unkey-env',
'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/unkey-env ./cmd/unkey-env && docker build -t unkey-env:latest -f Dockerfile.unkey-env .',
deps=['./cmd/unkey-env', './pkg/secrets', './pkg/cli', './Dockerfile.unkey-env'],
labels=['unkey'],
'build-inject',
'docker build -t inject:latest -f cmd/inject/Dockerfile .',
deps=['./cmd/inject', './pkg/secrets', './pkg/cli', './cmd/inject/Dockerfile'],
labels=['build'],
)

docker_build_with_restart(
Expand All @@ -304,7 +304,7 @@ if start_preflight:

k8s_yaml('k8s/manifests/preflight.yaml')

preflight_deps = ['unkey-compile', 'preflight-tls', 'unkey-env']
preflight_deps = ['build-unkey', 'preflight-tls', 'build-inject']
if start_krane: preflight_deps.append('krane')

k8s_resource(
Expand Down
57 changes: 57 additions & 0 deletions cmd/inject/.goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# Run from repo root: goreleaser release --config cmd/inject/.goreleaser.yaml
version: 2

project_name: inject

# Skip Go builds - the Dockerfile handles compilation via multi-stage build
builds:
- skip: true

dockers:
- image_templates:
- "ghcr.io/unkeyed/inject:{{ .Version }}-amd64"
dockerfile: cmd/inject/Dockerfile
use: buildx
build_flag_templates:
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--platform=linux/amd64"

- image_templates:
- "ghcr.io/unkeyed/inject:{{ .Version }}-arm64"
dockerfile: cmd/inject/Dockerfile
use: buildx
build_flag_templates:
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--platform=linux/arm64"

docker_manifests:
- name_template: "ghcr.io/unkeyed/inject:{{ .Version }}"
image_templates:
- "ghcr.io/unkeyed/inject:{{ .Version }}-amd64"
- "ghcr.io/unkeyed/inject:{{ .Version }}-arm64"

- name_template: "ghcr.io/unkeyed/inject:latest"
image_templates:
- "ghcr.io/unkeyed/inject:{{ .Version }}-amd64"
- "ghcr.io/unkeyed/inject:{{ .Version }}-arm64"

snapshot:
version_template: "{{ incpatch .Version }}-next"

changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
- "^chore:"

release:
prerelease: auto
29 changes: 29 additions & 0 deletions cmd/inject/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Build stage
FROM golang:1.25 AS builder

RUN apt-get update && apt-get install -y upx-ucl

WORKDIR /app

# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Build the binary with all optimizations
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /inject ./cmd/inject \
&& upx --best --lzma /inject

# Final stage - minimal image with CA certs for HTTPS calls
FROM alpine:3.19

RUN apk --no-cache add ca-certificates

COPY --from=builder /inject /inject

LABEL org.opencontainers.image.source=https://github.com/unkeyed/unkey
LABEL org.opencontainers.image.description="Unkey Secrets Injector"

ENTRYPOINT ["/inject"]
54 changes: 54 additions & 0 deletions cmd/inject/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

import (
"context"

"github.com/unkeyed/unkey/pkg/cli"
"github.com/unkeyed/unkey/pkg/secrets/provider"
)

var cmd = &cli.Command{
Name: "inject",
Usage: "Fetch secrets and exec the given command",
AcceptsArgs: true,
Flags: []cli.Flag{
cli.String("provider", "Secrets provider type",
cli.Default(string(provider.KraneVault)),
cli.EnvVar("UNKEY_PROVIDER")),
cli.String("endpoint", "Provider API endpoint",
cli.EnvVar("UNKEY_PROVIDER_ENDPOINT")),
cli.String("deployment-id", "Deployment ID",
cli.EnvVar("UNKEY_DEPLOYMENT_ID")),
cli.String("environment-id", "Environment ID for decryption",
cli.EnvVar("UNKEY_ENVIRONMENT_ID")),
cli.String("secrets-blob", "Base64-encoded encrypted secrets blob",
cli.EnvVar("UNKEY_ENCRYPTED_ENV")),
cli.String("token", "Authentication token",
cli.EnvVar("UNKEY_TOKEN")),
cli.String("token-path", "Path to token file",
cli.EnvVar("UNKEY_TOKEN_PATH")),
cli.Bool("debug", "Enable debug logging",
cli.EnvVar("UNKEY_DEBUG")),
},
Action: action,
}

func action(ctx context.Context, c *cli.Command) error {
cfg := config{
Provider: provider.Type(c.String("provider")),
Endpoint: c.String("endpoint"),
DeploymentID: c.String("deployment-id"),
EnvironmentID: c.String("environment-id"),
Encrypted: c.String("secrets-blob"),
Token: c.String("token"),
TokenPath: c.String("token-path"),
Debug: c.Bool("debug"),
Args: c.Args(),
}

if err := cfg.validate(); err != nil {
return cli.Exit(err.Error(), 1)
}

return run(ctx, cfg)
}
55 changes: 55 additions & 0 deletions cmd/inject/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

import (
"fmt"

"github.com/unkeyed/unkey/pkg/assert"
"github.com/unkeyed/unkey/pkg/secrets/provider"
)

// allowedUnkeyVars are environment variables that should NOT be cleared before exec.
// All other UNKEY_* vars are removed to prevent leaking sensitive config to the child process.
var allowedUnkeyVars = map[string]bool{
"UNKEY_DEPLOYMENT_ID": true,
"UNKEY_ENVIRONMENT_ID": true,
"UNKEY_REGION": true,
"UNKEY_INSTANCE_ID": true,
}

type config struct {
Provider provider.Type
Endpoint string
DeploymentID string
EnvironmentID string
Encrypted string
Token string
TokenPath string
Debug bool
Args []string
}

func (c *config) hasSecrets() bool {
return c.Encrypted != ""
}

func (c *config) validate() error {
if err := assert.True(len(c.Args) > 0, "command is required"); err != nil {
return err
}

if !c.hasSecrets() {
return nil
}

switch c.Provider {
case provider.KraneVault:
return assert.All(
assert.True((c.Token != "") != (c.TokenPath != ""), "exactly one of token or token-path is required"),
assert.NotEmpty(c.EnvironmentID, "environment-id is required when secrets-blob is provided"),
assert.NotEmpty(c.Endpoint, "endpoint is required for krane-vault provider"),
assert.NotEmpty(c.DeploymentID, "deployment-id is required for krane-vault provider"),
)
default:
return fmt.Errorf("unknown provider: %s", c.Provider)
}
}
14 changes: 14 additions & 0 deletions cmd/inject/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

import (
"context"
"fmt"
"os"
)

func main() {
if err := cmd.Run(context.Background(), os.Args); err != nil {
fmt.Fprintf(os.Stderr, "inject: %v\n", err)
os.Exit(1)
}
}
Loading
Loading