linode / linode/apl-api

feat: SSH authentication for the values repository

Open
#1,011 0 comments 0 reactions 0 assignees View on GitHub
Story
Dominant language
TypeScript
Stars
2
Forks
1
Avg merge
6d 2h
Merged PRs (30d)
8

Description

## Problem Statement

Customers who manage their platform configuration in private Git repositories over SSH are blocked from using the platform's GitOps workflow. The current implementation only supports HTTPS-based authentication for the values repository, requiring a username and personal access token. Teams that operate SSH-only Git hosts, enforce SSH-only access policies, or prefer SSH for security reasons cannot adopt the platform's GitOps workflow at all.

## Solution

Allow operators to configure the values repository using an SSH URL (`git@host:org/repo.git`) and supply a mounted SSH private key file. When an SSH URL is detected, the API authenticates all git operations (clone, pull, push) using the key file rather than embedding credentials in the URL. HTTPS authentication continues to work unchanged.

## User Stories

1. As a platform operator, I want to set `GIT_REPO_URL` to a `git@` SSH URL, so that the API connects to the values repository using SSH instead of HTTPS.
2. As a platform operator, I want to mount an SSH private key as a Kubernetes Secret volume, so that the key is managed by Kubernetes and never appears in env vars or process listings.
3. As a platform operator, I want to point `GIT_SSH_KEY_PATH` to the mounted key file, so that the API knows which key to use for SSH authentication.
4. As a platform operator, I want `GIT_USER` and `GIT_PASSWORD` to be optional when using SSH, so that I don't have to supply dummy HTTPS credentials alongside the SSH key.
5. As a platform operator, I want the API to fail at startup with a clear error when `GIT_REPO_URL` is a `git@` URL but `GIT_SSH_KEY_PATH` is not set, so that misconfiguration is diagnosed immediately rather than surfacing as a cryptic SSH permission error.
6. As a platform operator, I want SSH to work for all values repository operations (clone at startup, pull before worktree creation, push after commit), so that the full GitOps workflow functions without HTTPS fallback.
7. As a platform operator, I want SSH config to propagate automatically from the main repository to session worktrees, so that concurrent write sessions authenticate correctly without additional configuration.
8. As a platform operator, I want the API to skip the Kubernetes credential refresh loop when using SSH, so that the retry logic does not attempt to read a `GIT_PASSWORD` that does not exist.
9. As a platform operator, I want HTTPS authentication to continue working exactly as before, so that existing deployments are not affected by this change.
10. As a platform operator, I want the auth method to be inferred from the URL shape (`git@` → SSH, `https://` → HTTPS), so that I don't need to set an additional auth-method flag.

## Implementation Decisions

- **Auth method detection by URL shape.** If `GIT_REPO_URL` starts with `git@`, SSH authentication is used. If it starts with `https://`, HTTPS authentication is used. No explicit flag required.

- **SSH key delivered as a mounted file.** The SSH private key is supplied via a Kubernetes Secret volume mount. `GIT_SSH_KEY_PATH` holds the path to that file. The API reads the path and passes it directly to `GIT_SSH_COMMAND` — it never copies or rewrites the key. (This contrasts with the existing `codeRepoUtils` pattern for team code repositories, which writes key content from a secret into a temp file. That pattern is acceptable for short-lived test connections; the values repository connection is long-lived and the temp-file lifecycle is harder to reason about. See ADR `docs/adr/0001-ssh-auth-via-mounted-key-file.md`.)

- **`GIT_SSH_COMMAND` set on the `SimpleGit` instance.** SSH authentication is applied by calling `.env('GIT_SSH_COMMAND', 'ssh -i -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null')` on the `SimpleGit` instance in the `Git` constructor. This is consistent with how `codeRepoUtils.ts` configures SSH for team code repositories.

- **`StrictHostKeyChecking` disabled.** Host key verification is not enforced. The values repository connection runs inside a Kubernetes pod on operator-controlled infrastructure. This is consistent with the existing `codeRepoUtils.ts` SSH pattern. Known-hosts support is explicitly out of scope.

- **`sshKeyPath` passed as an explicit parameter** through the `getRepo` factory and into the `Git` constructor, where it is stored as a field. The constructor detects the SSH URL and sets `GIT_SSH_COMMAND` using the supplied key path. Worktree repos created via `getWorktreeRepo` receive `sshKeyPath` from the main repo instance, so SSH config propagates automatically.

- **`GIT_USER` and `GIT_PASSWORD` become optional** (validators gain `default: undefined`). A new `GIT_SSH_KEY_PATH` validator is added (also optional, `default: undefined`).

- **Fail-fast validation in `getRepo`.** If the URL is a `git@` SSH URL and `sshKeyPath` is falsy, `getRepo` throws immediately with a descriptive error before any git operation is attempted.

- **Credential refresh skipped in SSH mode.** The `initGit` retry loop in `OtomiStack` currently re-reads `GIT_PASSWORD` from the `otomi-api-git-credentials` Kubernetes secret on each retry. This refresh is skipped when the URL is a `git@` URL.

- **`getUrl` / `getUrlAuth` helpers bypassed for SSH URLs.** These helpers assume an `://` scheme and would corrupt a `git@` URL. The `getRepo` factory skips normalization and auth-URL construction when the URL starts with `git@`.

- **Migration path not affected.** `pushToNewRemote`, `testRemoteConnection`, and `commitAndPushMigration` are unchanged. SSH support for the migration flow is out of scope.

## Testing Decisions

Good tests for this feature verify the external behavior of the `Git` class and `getRepo` factory — specifically, which environment variables are set on the `SimpleGit` instance and whether errors are thrown at the right point. Tests should not assert on internal method call order or private implementation details.

**Module: `src/git.ts` — tested in `src/git.test.ts`**

- `Git` constructor with SSH URL + `sshKeyPath`: assert `simpleGit(...).env('GIT_SSH_COMMAND', ...)` is called with the correct key path and flags.
- `Git` constructor with HTTPS URL: assert `GIT_SSH_COMMAND` is not set (regression guard).
- `getRepo` with SSH URL and no `sshKeyPath`: assert it throws with a message containing `"GIT_SSH_KEY_PATH"`.
- `getWorktreeRepo` with SSH main repo: assert the returned worktree `Git` instance also has `GIT_SSH_COMMAND` set (SSH config propagates).

Prior art: all existing tests in `src/git.test.ts` follow this pattern — `simpleGit` is mocked at the module level, `Git` is constructed directly, and assertions are made on the mock's `.env()` calls.

## Out of Scope

- SSH authentication for the **migration flow** (`pushToNewRemote`, `testRemoteConnection`, `commitAndPushMigration`).
- SSH authentication for **team code repositories** (`AplTeamCodeRepo`) — already supported via `codeRepoUtils.ts`.
- **Host key verification** (`StrictHostKeyChecking`) — disabled unconditionally; a `known_hosts` mechanism may be added as a separate hardening step.
- **SSH key rotation** without pod restart — the key is a mounted file; rotation requires a pod restart or volume re-mount.
- **SSH agent forwarding** or any auth method other than a static key file.

## Further Notes

The domain glossary (`CONTEXT.md`) and the architectural decision (`docs/adr/0001-ssh-auth-via-mounted-key-file.md`) were created as part of scoping this feature and should be committed alongside the implementation.

Pod spec changes required for SSH-mode deployments:
- Mount the SSH private key Secret as a volume.
- Set `GIT_REPO_URL` to a `git@` SSH URL.
- Set `GIT_SSH_KEY_PATH` to the volume mount path.
- Omit or leave empty `GIT_USER` and `GIT_PASSWORD`.

Contributor guide

Open the contributing guide

Research direction

Start with src/git.ts and src/git.test.ts, running the existing tests to understand the mocked SimpleGit behavior. Trace getRepo, getWorktreeRepo, and OtomiStack.initGit, along with the environment validators and docs/adr/0001-ssh-auth-via-mounted-key-file.md. Done means SSH and HTTPS repository flows both work, SSH configuration reaches worktrees, missing keys fail early, and the specified regression tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, kubernetes, typescript
Domain
backend, devops
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.