block / block/cachew

Support external Git credential providers for non-GitHub repositories

Open
#404 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
41
Forks
12
PR merge metrics
No merged PRs in 30d

Description

## Background

We want to reuse Cachew for private Git repositories hosted outside GitHub. Authentication for these repositories is not based on a GitHub App, so the existing GitHub-specific integration cannot provide the required credentials.

Cachew should support repository-scoped external credential providers without embedding provider-specific authentication logic in the main process. Providers should return a complete HTTP authorization value so repositories can use schemes such as Basic or Bearer authentication.

## Proposed configuration

Add a repeatable `git-credential-command` block:

```hcl
git-credential-command "private-git" {
command = [
"/usr/local/bin/private-git-credential",
"--provider-option", "value",
]

remotes = [
"https://git.example.com/project/repository",
]

timeout = "5s"
refresh-before = "5m"
}
```

Configured repository URLs are canonicalized and matched exactly. A provider's credential must not be applied to a repository that was not assigned to it.

## Proposed internal interface

```go
type Credential struct {
Authorization string
URLScope string
}

type Provider interface {
Credential(
ctx context.Context,
repositoryURL string,
) (credential Credential, matched bool, err error)
}
```

`Authorization` is the complete HTTP authorization value, for example `Basic ...` or `Bearer ...`. `URLScope` is the canonical repository URL to which it may be applied.

Providers can be composed in order, stopping at the first match. Existing in-process authentication integrations can be adapted to this interface without invoking a plugin binary.

A matched provider failure should fail closed rather than falling back to unauthenticated access. Unmatched repositories retain the existing authentication behavior.

## Plugin interface

A plugin implements credential acquisition for one canonical repository URL:

```go
type CommandResult struct {
Authorization string
ExpiresAt time.Time
}

type CommandHandler interface {
Credential(ctx context.Context, remoteURL string) (CommandResult, error)
}
```

A helper library can expose `ServeCommand`, request/response encoding functions, and an optional CLI entry point for Go implementations. Plugins may be written in any language as long as they implement the protocol.

## Main process and plugin protocol

Cachew executes the configured command directly without a shell and applies the configured timeout. One request and one response are exchanged per invocation.

Cachew writes one newline-terminated JSON object to stdin:

```json
{"version":1,"remote_url":"https://git.example.com/project/repository"}
```

The plugin writes one newline-terminated JSON object to stdout:

```json
{"version":1,"authorization":"Bearer example-token","expires_at":"2026-08-10T12:00:00Z"}
```

Proposed schemas:

```go
const ProtocolVersion = 1

type Request struct {
Version int `json:"version"`
RemoteURL string `json:"remote_url"`
}

type Response struct {
Version int `json:"version"`
Authorization string `json:"authorization"`
ExpiresAt time.Time `json:"expires_at"`
}
```

Protocol requirements:

- `remote_url` is the canonical upstream repository URL.
- `authorization` contains the complete HTTP `Authorization` header value.
- `expires_at` is a future RFC 3339 timestamp.
- Requests and responses are size-limited and strictly decoded.
- Unknown fields, extra JSON values, unsupported versions, invalid authorization values, and expired credentials are rejected.
- Plugin failures, timeouts, unsuccessful exits, and invalid responses fail the matched operation.

## Credential lifecycle

Successful credentials are cached only in memory:

1. Canonicalize the requested repository URL and select its exact provider match.
2. Return an unexpired cached credential when available.
3. Refresh credentials within the configured `refresh-before` window.
4. Coalesce concurrent refreshes for the same provider and repository.
5. Scope the returned authorization to the canonical repository URL.
6. Discard credentials after expiration; never persist them.

## Applying credentials

The authorization should be applied to Git as repository-scoped HTTP configuration through `GIT_CONFIG_*` environment entries:

```text
GIT_CONFIG_KEY_=http..extraHeader
GIT_CONFIG_VALUE_=Authorization:
```

This supports Basic and Bearer credentials without a shell-form credential helper or credentials in Git subprocess arguments. This part is related to #402.

The same provider should authenticate:

- Background clone, fetch, and mirror operations.
- Direct upstream Git HTTP requests.
- Cache-miss and stale-reference fallbacks.
- Push requests.
- Git LFS requests.

## Security properties

- Exact canonical repository matching and URL scoping.
- No shell execution for provider commands.
- No credentials in subprocess command-line arguments.
- In-memory caching only.
- Credentials are never logged or written to stderr.
- Strict protocol and authorization-value validation.
- Fail-closed behavior for matched provider failures.

Contributor guide

Open the contributing guide

Research direction

The issue names no implementation files or tests; start by tracing the existing GitHub authentication and repository-scoped Git HTTP paths, including the work related to #402. Check the background clone, fetch, mirror, push, Git LFS, and cache-miss or stale-reference entry points. Done means all listed operations use exact, scoped credentials with strict protocol validation and fail-closed behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
authentication, backend, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.