mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-03 05:07:10 +00:00
* Add SSH keys and config to credential sharing Private keys have lived as plaintext-on-disk files guarded only by a passphrase. This puts them in the same end-to-end-encrypted vaults as .env secrets, and adds an agent path so a machine can use a key without ever writing one to its disk. - `ssh` provider: ~/.ssh as a value bag. Files are picked by sniffing contents (PRIVATE KEY blocks, ssh-*/ecdsa-*/sk-* public keys) plus config, config.d/* and allowed_signers. known_hosts and authorized_keys are host-specific and access-granting, so they need an explicit --include. - Each file is one secret carrying a JSON envelope of path, mode and body. The engine only hands write() the secrets that CHANGED, so a separate manifest secret would be absent whenever a key's contents change but the file list doesn't — self-describing values keep every restore total. - `logicsrc secrets ssh push|pull|list|agent`, addressed by PERSON not project: the vault is ssh--<username>, which teams vaults reads as project ssh, env <username>. One teammate's keys never land in another's restore; sharing stays a deliberate teams grant. - Both directions hold back anything that would overwrite a file that already differs, and say what they skipped. --force opts in. A restore onto a machine with its own keys is otherwise a way to lose them. - Restores chmod each file back to its recorded mode; writeFileSync's mode applies only on create, so an existing world-readable key would otherwise stay world-readable. The adapter declares delete:false. - push warns about passphrase-less private keys before they go up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Add worked examples to secrets and secrets ssh help Commander's usage line shows only the first alias, so `logicsrc secrets` — the spelling people actually type — was invisible in its own help. The examples carry it, alongside the flows worth copying: link/up/down, the ssh backup round trip, and a plan → dry-run → approve sync. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Advertise the ssh provider on the marketing page The marketing-drift contract failed the build because `ssh` shipped in the provider registry with no entry in MARKETING_PROOF -- which is the test working: it exists so a provider cannot ship while the pages people actually land on still describe the tool without it. The proof regex is `/~\/\.ssh|SSH key/` rather than a bare `/SSH/` on purpose. The provider grid renders every registry `name`, and this one is "Local SSH directory", so `/SSH/` would already be satisfied by the generated grid and the provider could ship with no copy written about it at all -- passing the test while failing its intent. Requiring the path or the phrase means a human wrote a sentence. That sentence is the new block in the credential-sharing band: ~/.ssh is a directory of files whose permission bits are load-bearing, not a set of KEY=VALUE lines, which is the part that makes this provider different from the other six. README already named ~/.ssh keys, so it needed no change. apps/logicsrc-web: 75/75 contract tests pass (was 74 passed, 1 failed). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
123 lines
3.6 KiB
TypeScript
123 lines
3.6 KiB
TypeScript
import type { PluginDefinition } from "@logicsrc/plugin-core";
|
|
import { credentialSharingManifest } from "./manifest.js";
|
|
import { CredentialEngine, type CredentialEngineOptions } from "./engine.js";
|
|
import { listCredentialProviderManifests } from "./providers/index.js";
|
|
|
|
export const credentialSharingPlugin: PluginDefinition = {
|
|
manifest: credentialSharingManifest,
|
|
configDefaults: {
|
|
enabled: true,
|
|
default_policy: "approval_required_for_destructive",
|
|
credential_home: "${LOGICSRC_CREDENTIAL_HOME}"
|
|
},
|
|
routes: [
|
|
{ method: "GET", path: "/api/credentials/providers", capability: "credentials.providers.list" },
|
|
{ method: "GET", path: "/api/credentials/inspect", capability: "credentials.inspect" },
|
|
{ method: "POST", path: "/api/credentials/diff", capability: "credentials.diff" },
|
|
{ method: "POST", path: "/api/credentials/plans", capability: "credentials.plan" },
|
|
{ method: "POST", path: "/api/credentials/plans/:id/approve", capability: "credentials.approve" },
|
|
{ method: "POST", path: "/api/credentials/plans/:id/sync", capability: "credentials.sync" },
|
|
{ method: "POST", path: "/api/credentials/runs/:id/rollback", capability: "credentials.rollback" },
|
|
{ method: "GET", path: "/api/credentials/runs/:id/audit", capability: "credentials.audit.read" }
|
|
],
|
|
permissions: [
|
|
"credentials:inspect",
|
|
"credentials:diff",
|
|
"credentials:plan",
|
|
"credentials:approve",
|
|
"credentials:sync",
|
|
"credentials:rollback",
|
|
"credentials:audit:read"
|
|
],
|
|
tuiPanels: [{ id: "credential-sharing", title: "Credential Sharing" }]
|
|
};
|
|
|
|
/** Factory mirroring the LogicSRC Credential Sharing SDK spec. */
|
|
export function createCredentialEngine(options: CredentialEngineOptions = {}): CredentialEngine {
|
|
return new CredentialEngine(options);
|
|
}
|
|
|
|
/** Provider listing without constructing an engine (used by the CLI `providers` command). */
|
|
export function listCredentialProviders() {
|
|
return listCredentialProviderManifests();
|
|
}
|
|
|
|
export { credentialSharingManifest };
|
|
export { CredentialEngine, DEFAULT_CREDENTIAL_POLICY, endpointLabel } from "./engine.js";
|
|
export type { CredentialEngineOptions } from "./engine.js";
|
|
export {
|
|
credentialProviders,
|
|
credentialProviderRegistry,
|
|
listCredentialProviderManifests,
|
|
envProvider,
|
|
dopplerProvider,
|
|
railwayProvider,
|
|
githubSecretsProvider,
|
|
sshProvider,
|
|
teamProvider,
|
|
parseEnv,
|
|
applyEnv,
|
|
classifySshFile,
|
|
decodeSshFile,
|
|
defaultSshDirectory,
|
|
encodeSshFile,
|
|
isPassphraseless,
|
|
readSshDirectory,
|
|
secretNameForPath,
|
|
sshDirectory,
|
|
SSH_ENVELOPE_VERSION,
|
|
type SshFile,
|
|
type SshFileKind
|
|
} from "./providers/index.js";
|
|
export {
|
|
TeamClient,
|
|
TeamApiError,
|
|
type TeamClientOptions,
|
|
type RemoteUser,
|
|
type RemoteTeam,
|
|
type RemoteMember,
|
|
type RemoteVault,
|
|
type RemoteSecret,
|
|
type RemoteGrantRow
|
|
} from "./client.js";
|
|
export {
|
|
generateIdentityKeyPair,
|
|
generateVaultKey,
|
|
wrapVaultKey,
|
|
unwrapVaultKey,
|
|
encryptValue,
|
|
decryptValue,
|
|
publicKeyForSecret,
|
|
type IdentityKeyPair,
|
|
type SealedValue
|
|
} from "./crypto.js";
|
|
export {
|
|
loadOrCreateIdentity,
|
|
readIdentity,
|
|
saveIdentity,
|
|
updateIdentity,
|
|
requireAuth,
|
|
verifyIdentityIntegrity,
|
|
identityPath,
|
|
logicsrcHome,
|
|
defaultApiUrl,
|
|
envApiUrl,
|
|
resolveApiUrl,
|
|
DEFAULT_API_URL,
|
|
type LocalIdentity
|
|
} from "./identity.js";
|
|
export {
|
|
createFileCredentialStore,
|
|
createMemoryCredentialStore,
|
|
defaultCredentialHome,
|
|
type CredentialStore
|
|
} from "./store.js";
|
|
export { fingerprintValue, fingerprintsEqual } from "./fingerprint.js";
|
|
export {
|
|
planVaultRekey,
|
|
type RekeyPlan,
|
|
type RekeyPlanInput,
|
|
type RekeyMember,
|
|
type SealedSecret
|
|
} from "./rekey.js";
|
|
export * from "./types.js";
|