sliftutils 1.7.130 → 1.7.132
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +4 -51
- package/package.json +1 -1
- package/security/authorizedKeys/authorizedKeys.d.ts +37 -0
- package/security/authorizedKeys/daemon/changes.d.ts +17 -0
- package/security/authorizedKeys/daemon/git.d.ts +35 -0
- package/security/authorizedKeys/daemon/notify.d.ts +8 -0
- package/security/authorizedKeys/daemon/paths.d.ts +15 -0
- package/security/authorizedKeys/daemon/repoFiles.d.ts +25 -0
- package/security/authorizedKeys/daemon/revocation.d.ts +74 -0
- package/security/authorizedKeys/daemon/sessions.d.ts +19 -0
- package/security/authorizedKeys/daemon/state.d.ts +67 -0
- package/security/authorizedKeys/daemon/trust.d.ts +20 -0
- package/security/authorizedKeys/keysRepo.d.ts +10 -0
- package/security/authorizedKeys/revokeSource.d.ts +13 -0
- package/security/authorizedKeys/sources.d.ts +17 -0
- package/security/helpers/paths.d.ts +4 -0
- package/security/helpers/spawn.d.ts +16 -0
- package/security/keys/deriveKey.d.ts +16 -0
- package/security/keys/sshKeyFile.d.ts +18 -0
- package/security/machines/addMachine.ts +76 -24
- package/security/machines/dist/addMachine.ts.cache +6 -5
- package/security/machines/dist/machines.ts.cache +80 -13
- package/security/machines/machines.d.ts +81 -0
- package/security/machines/machines.ts +89 -14
- package/security/notifications/discord.d.ts +36 -0
- package/security/signedFiles/manifest.d.ts +44 -0
- package/storage/remoteStorage/ArchivesRemote.ts +8 -10
- package/storage/remoteStorage/accessPage.tsx +13 -90
- package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +11 -12
- package/storage/remoteStorage/dist/serverConfig.ts.cache +7 -49
- package/storage/remoteStorage/dist/storageController.ts.cache +33 -114
- package/storage/remoteStorage/serverConfig.d.ts +0 -10
- package/storage/remoteStorage/serverConfig.ts +6 -48
- package/storage/remoteStorage/storageController.d.ts +4 -36
- package/storage/remoteStorage/storageController.ts +40 -126
- package/storage/remoteStorage/storageServer.ts +1 -8
- package/storage/remoteStorage/storageServerCli.ts +14 -0
- package/storage/remoteStorage/grantAccess.js +0 -4
- package/storage/remoteStorage/grantAccessCli.d.ts +0 -1
- package/storage/remoteStorage/grantAccessCli.ts +0 -25
package/index.d.ts
CHANGED
|
@@ -4192,11 +4192,6 @@ declare module "sliftutils/storage/remoteStorage/deployTakeover" {
|
|
|
4192
4192
|
|
|
4193
4193
|
}
|
|
4194
4194
|
|
|
4195
|
-
declare module "sliftutils/storage/remoteStorage/grantAccessCli" {
|
|
4196
|
-
export {};
|
|
4197
|
-
|
|
4198
|
-
}
|
|
4199
|
-
|
|
4200
4195
|
declare module "sliftutils/storage/remoteStorage/intermediateManagement" {
|
|
4201
4196
|
import { RemoteConfig } from "../IArchives";
|
|
4202
4197
|
/** Called every time a store applies a routing config to itself (see BlobStore's onRoutingApplied): arms the scans the config's upcoming window boundaries need. Each scan is scheduled once - the key includes the boundary it is for - so re-arming on every config application is harmless. */
|
|
@@ -4307,14 +4302,10 @@ declare module "sliftutils/storage/remoteStorage/remoteConfig" {
|
|
|
4307
4302
|
}
|
|
4308
4303
|
|
|
4309
4304
|
declare module "sliftutils/storage/remoteStorage/serverConfig" {
|
|
4310
|
-
import type { IStorage } from "../IStorage";
|
|
4311
|
-
import type { AccessRequest, TrustRecord } from "./storageController";
|
|
4312
4305
|
export type StorageServerConfig = {
|
|
4313
4306
|
domain: string;
|
|
4314
4307
|
port: number;
|
|
4315
4308
|
rootDomain: string;
|
|
4316
|
-
sshTarget: string;
|
|
4317
|
-
serverCommand: string;
|
|
4318
4309
|
folder: string;
|
|
4319
4310
|
};
|
|
4320
4311
|
export declare function setStorageServerConfig(value: StorageServerConfig): void;
|
|
@@ -4324,12 +4315,6 @@ declare module "sliftutils/storage/remoteStorage/serverConfig" {
|
|
|
4324
4315
|
export declare function getWritesRejectedReason(): string | undefined;
|
|
4325
4316
|
export declare function assertWritesAllowed(): void;
|
|
4326
4317
|
export declare function getStorageFolder(): string;
|
|
4327
|
-
export declare function getTrust(): Promise<IStorage<TrustRecord>>;
|
|
4328
|
-
export declare function getRequests(): Promise<IStorage<AccessRequest[]>>;
|
|
4329
|
-
export declare function setTrustedMachines(config: {
|
|
4330
|
-
account: string;
|
|
4331
|
-
machineIds: string[];
|
|
4332
|
-
}): Promise<void>;
|
|
4333
4318
|
export declare function addExtraListenPort(port: number): void;
|
|
4334
4319
|
export declare function removeExtraListenPort(port: number): void;
|
|
4335
4320
|
/** Whether address:port is this server process, including its extra listen ports (a deploy switchover's alternate port is still us). Used to tell which config entries are OUR copy of a bucket - the stores we run - as opposed to peers we synchronize with. Talking to ourselves is not one of the things it prevents: a source that happens to be us is reached over the API like any other. */
|
|
@@ -4437,6 +4422,7 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
|
4437
4422
|
/// <reference types="node" />
|
|
4438
4423
|
import { ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus, FindConfig, SourceConfig } from "../IArchives";
|
|
4439
4424
|
import { ActiveBucketInfo, ServerBucketInfo } from "./storageServerState";
|
|
4425
|
+
import { MachineState } from "../../security/machines/machines";
|
|
4440
4426
|
import { AccessTotals, AccessSummaryState } from "./accessStats";
|
|
4441
4427
|
import { LogFileInfo } from "../StreamingLogs";
|
|
4442
4428
|
import type { SummaryEntry } from "../../treeSummary";
|
|
@@ -4454,25 +4440,13 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
|
4454
4440
|
signature: string;
|
|
4455
4441
|
data: AuthTokenData;
|
|
4456
4442
|
};
|
|
4457
|
-
export type AccessRequest = {
|
|
4458
|
-
requestId: string;
|
|
4459
|
-
account: string;
|
|
4460
|
-
machineId: string;
|
|
4461
|
-
ip: string;
|
|
4462
|
-
time: number;
|
|
4463
|
-
};
|
|
4464
|
-
export type TrustRecord = {
|
|
4465
|
-
account: string;
|
|
4466
|
-
machineId: string;
|
|
4467
|
-
ip: string;
|
|
4468
|
-
time: number;
|
|
4469
|
-
};
|
|
4470
4443
|
export type AccessState = {
|
|
4471
4444
|
machineId: string;
|
|
4472
4445
|
ip: string;
|
|
4473
4446
|
hasAccess: boolean;
|
|
4474
|
-
|
|
4475
|
-
|
|
4447
|
+
reason?: string;
|
|
4448
|
+
addMachineCommand?: string;
|
|
4449
|
+
trustedMachines?: MachineState[];
|
|
4476
4450
|
};
|
|
4477
4451
|
export declare function broadcastRoutingChanged(): void;
|
|
4478
4452
|
export declare const RemoteStorageController: import("socket-function/SocketFunctionTypes").SocketRegistered<{
|
|
@@ -4481,34 +4455,13 @@ declare module "sliftutils/storage/remoteStorage/storageController" {
|
|
|
4481
4455
|
machineId: string;
|
|
4482
4456
|
ip: string;
|
|
4483
4457
|
}>;
|
|
4484
|
-
requestAccess: (config: {
|
|
4485
|
-
account: string;
|
|
4486
|
-
}) => Promise<{
|
|
4487
|
-
machineId: string;
|
|
4488
|
-
ip: string;
|
|
4489
|
-
requestId: string;
|
|
4490
|
-
grantAccessCommand: string;
|
|
4491
|
-
}>;
|
|
4492
4458
|
getAccessState: (config: {
|
|
4493
4459
|
account: string;
|
|
4494
4460
|
}) => Promise<AccessState>;
|
|
4495
|
-
listRequestsForIP: (config: {
|
|
4496
|
-
account: string;
|
|
4497
|
-
ip: string;
|
|
4498
|
-
}) => Promise<AccessRequest[]>;
|
|
4499
|
-
grantAccess: (config: {
|
|
4500
|
-
requestId: string;
|
|
4501
|
-
}) => Promise<TrustRecord>;
|
|
4502
4461
|
adminListActiveBuckets: () => Promise<{
|
|
4503
4462
|
account: string;
|
|
4504
4463
|
bucketName: string;
|
|
4505
4464
|
}[]>;
|
|
4506
|
-
adminListRequests: (config: {
|
|
4507
|
-
ip: string;
|
|
4508
|
-
}) => Promise<AccessRequest[]>;
|
|
4509
|
-
adminGrantAccess: (config: {
|
|
4510
|
-
requestId: string;
|
|
4511
|
-
}) => Promise<TrustRecord>;
|
|
4512
4465
|
get2: (config: {
|
|
4513
4466
|
account: string;
|
|
4514
4467
|
bucketName: string;
|
package/package.json
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare function normalizeKeys(contents: string): string[];
|
|
2
|
+
/** The fingerprint ssh itself reports for a key, which is what the sshd log names and therefore
|
|
3
|
+
what a revocation is keyed by. Returns "" for a line that holds no key. */
|
|
4
|
+
export declare function keyFingerprint(keyLine: string): string;
|
|
5
|
+
export declare const NO_RESTRICTION = "ANY ADDRESS (no from= restriction)";
|
|
6
|
+
/** The addresses a key may be used from, one by one, or undefined when the key carries no from=
|
|
7
|
+
at all. Undefined and an empty list are very different things, so they stay distinguishable. */
|
|
8
|
+
export declare function keyRestrictionList(keyLine: string): string[] | undefined;
|
|
9
|
+
/** The addresses a key may be used from, which is the part of an authorized_keys line that
|
|
10
|
+
decides how much a stolen key is worth. A key with no restriction says so loudly. */
|
|
11
|
+
export declare function keyRestriction(keyLine: string): string;
|
|
12
|
+
/** Adds addresses to a key's from= list, leaving the rest of the line exactly as it was. Returns
|
|
13
|
+
which addresses were actually new, so a caller can report only what it changed.
|
|
14
|
+
|
|
15
|
+
A key with no from= at all is left alone: adding one would silently restrict a key that is
|
|
16
|
+
currently unrestricted, which is a different decision than the one being made here. */
|
|
17
|
+
export declare function allowAddresses(keyLine: string, addresses: string[]): {
|
|
18
|
+
keyLine: string;
|
|
19
|
+
added: string[];
|
|
20
|
+
};
|
|
21
|
+
/** Enough to recognise whose key this is without printing the whole blob. */
|
|
22
|
+
export declare function summarizeKey(keyLine: string): string;
|
|
23
|
+
/** The shortest thing that still identifies a key to a person: the comment it carries, which is
|
|
24
|
+
usually user@machine, and otherwise the tail of the key itself. For the first line of a
|
|
25
|
+
notification, where there is only room for the one thing that matters. */
|
|
26
|
+
export declare function keyNiceName(keyLine: string): string;
|
|
27
|
+
/** What a set of keys has to satisfy before it is worth signing, as a list of complaints.
|
|
28
|
+
|
|
29
|
+
Every key needs a from=, because an unrestricted key can never be caught being used from the
|
|
30
|
+
wrong place, and being caught is the only thing that triggers a revocation.
|
|
31
|
+
|
|
32
|
+
No two keys may allow the same addresses, because that is one person holding two keys: revoking
|
|
33
|
+
one of them leaves the other working, so the revocation achieves nothing. */
|
|
34
|
+
export declare function findKeyProblems(keys: string[]): string[];
|
|
35
|
+
/** Reads the authorized keys a repo checkout wants applied. Prefers a top level authorized_keys
|
|
36
|
+
file and otherwise concatenates every .pub at the top level. */
|
|
37
|
+
export declare function readRepoKeys(repoPath: string): Promise<string[]>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** A reason important enough to name the whole message. Its headline replaces the one the change
|
|
2
|
+
itself would have got, because "somebody is using a key they should not" outranks "the keys
|
|
3
|
+
changed" in the one line a phone shows. */
|
|
4
|
+
export type LeadingReason = {
|
|
5
|
+
headline: string;
|
|
6
|
+
body: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function addChangeReason(reason: string): void;
|
|
9
|
+
/** For the one thing worth reading before anything else: somebody using a key from an address it
|
|
10
|
+
is not allowed from. */
|
|
11
|
+
export declare function addLeadingChangeReason(reason: LeadingReason): void;
|
|
12
|
+
/** The reasons gathered since the last write, and clears them. Called whether or not anything
|
|
13
|
+
changed, so reasons that came to nothing cannot show up against some later change. */
|
|
14
|
+
export declare function takeChangeReasons(): {
|
|
15
|
+
leading: LeadingReason[];
|
|
16
|
+
reasons: string[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** core.sshCommand keeps the key selection with the command instead of in the environment. */
|
|
2
|
+
export declare function runGit(config: {
|
|
3
|
+
args: string[];
|
|
4
|
+
cwd?: string;
|
|
5
|
+
keyPath: string;
|
|
6
|
+
allowFailure?: boolean;
|
|
7
|
+
}): Promise<{
|
|
8
|
+
stdout: string;
|
|
9
|
+
stderr: string;
|
|
10
|
+
status: number | undefined;
|
|
11
|
+
error: Error | undefined;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function repoIsUsable(config: {
|
|
14
|
+
repoPath: string;
|
|
15
|
+
keyPath: string;
|
|
16
|
+
}): Promise<boolean>;
|
|
17
|
+
/** Cloned beside the old checkout and swapped in, so a clone that fails leaves the copy we are
|
|
18
|
+
already using untouched rather than deleting the only keys we have. */
|
|
19
|
+
export declare function cloneRepo(config: {
|
|
20
|
+
repoURL: string;
|
|
21
|
+
repoPath: string;
|
|
22
|
+
keyPath: string;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
export declare function currentBranch(config: {
|
|
25
|
+
repoPath: string;
|
|
26
|
+
keyPath: string;
|
|
27
|
+
}): Promise<string>;
|
|
28
|
+
/** Returns what changed, so the caller can report it. A rewritten history is called out
|
|
29
|
+
separately - it means the remote no longer contains the commits we already had. */
|
|
30
|
+
export declare function syncRepo(repoURL: string): Promise<{
|
|
31
|
+
changed: boolean;
|
|
32
|
+
historyRewritten: boolean;
|
|
33
|
+
remoteSha: string;
|
|
34
|
+
previousSha: string;
|
|
35
|
+
}>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function setHostLabel(value: string): void;
|
|
2
|
+
/** A headline and the rest.
|
|
3
|
+
|
|
4
|
+
The headline is all a phone shows. It gets the whole preview to itself: no product name, no
|
|
5
|
+
host, no timestamp in front of it, because a person glancing at their lock screen needs to read
|
|
6
|
+
what happened, not which machine said it or when. Those go at the end, where they are there for
|
|
7
|
+
whoever opens the message and out of the way of whoever does not. */
|
|
8
|
+
export declare function notify(headline: string, body: string): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const CONFIG_PATH = "/etc/portsecure/daemon.json";
|
|
2
|
+
export declare const STATE_PATH = "/var/lib/portsecure/state.json";
|
|
3
|
+
export declare const KEYS_HISTORY_PATH = "/var/lib/portsecure/authorized-keys-history";
|
|
4
|
+
export declare const ROOT_AUTHORIZED_KEYS = "/root/.ssh/authorized_keys";
|
|
5
|
+
export declare const SSHD_CONFIG_PATH = "/etc/ssh/sshd_config";
|
|
6
|
+
export declare const SSHD_DROPIN_DIR = "/etc/ssh/sshd_config.d";
|
|
7
|
+
export declare const SSHD_DROPIN_PATH = "/etc/ssh/sshd_config.d/00-portsecure.conf";
|
|
8
|
+
export declare const PASSWD_PATH = "/etc/passwd";
|
|
9
|
+
export declare const AUTH_LOG_PATH = "/var/log/auth.log";
|
|
10
|
+
export declare const CHECK_INTERVAL: number;
|
|
11
|
+
export declare const WEBHOOK_CHECK_INTERVAL: number;
|
|
12
|
+
export declare const GIT_TIMEOUT: number;
|
|
13
|
+
export declare const MAX_ERROR_BODY_LENGTH = 500;
|
|
14
|
+
export declare const SIGNER_CHANGE_DELAY: number;
|
|
15
|
+
export declare const MAX_REPO_FAILURES_BEFORE_RECLONE = 15;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Reading a repo, made to look like reading a directory. A caller asks for a file and gets its
|
|
2
|
+
contents; keeping a checkout on disk, cloning it once and pulling it since, is this module's
|
|
3
|
+
business and nobody else's.
|
|
4
|
+
|
|
5
|
+
Nothing here ever answers "there is nothing" when the truth is "I could not look". A repo that
|
|
6
|
+
cannot be reached throws, and the caller skips that source, because reading an unreachable
|
|
7
|
+
revoke repo as empty would un-revoke every key in it. */
|
|
8
|
+
export type RepoRef = {
|
|
9
|
+
repoURL: string;
|
|
10
|
+
repoPath: string;
|
|
11
|
+
keyPath: () => Promise<string>;
|
|
12
|
+
};
|
|
13
|
+
export declare function sourceRepo(sourceURL: string): RepoRef;
|
|
14
|
+
export declare function revokeRepo(sourceURL: string): RepoRef;
|
|
15
|
+
/** The revoke repo's key is worked out from the source's, so nothing extra had to be uploaded and
|
|
16
|
+
nothing extra is stored anywhere it could be taken from. */
|
|
17
|
+
export declare function ensureRevokeKey(sourceURL: string): Promise<string>;
|
|
18
|
+
/** Brings the checkout in line with the remote. Cloned the first time, pulled after that, and the
|
|
19
|
+
usual case of nothing having changed costs one ref listing and no objects. Throws if the repo
|
|
20
|
+
cannot be read, so a caller cannot mistake that for an empty repo. */
|
|
21
|
+
export declare function syncRepoFiles(repo: RepoRef): Promise<void>;
|
|
22
|
+
/** One file out of the repo, or undefined if the repo does not have that file. */
|
|
23
|
+
export declare function readRepoFile(repo: RepoRef, filePath: string): Promise<string | undefined>;
|
|
24
|
+
/** The names in one of the repo's directories, or nothing if it has no such directory. */
|
|
25
|
+
export declare function listRepoDir(repo: RepoRef, directory: string): Promise<string[]>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export type Attempt = {
|
|
2
|
+
ip: string;
|
|
3
|
+
user: string;
|
|
4
|
+
port: string;
|
|
5
|
+
required: string;
|
|
6
|
+
line: string;
|
|
7
|
+
};
|
|
8
|
+
/** What an unrevoke has to name to undo a revocation, and what stops a second revocation of the
|
|
9
|
+
same key from the same address. */
|
|
10
|
+
export declare function pairKey(config: {
|
|
11
|
+
fingerprint: string;
|
|
12
|
+
ip: string;
|
|
13
|
+
}): string;
|
|
14
|
+
/** Unique to the event. The time is there to read, the random half is there to be unique. */
|
|
15
|
+
export declare function newRevocationId(fingerprint: string): string;
|
|
16
|
+
export type RevocationFile = {
|
|
17
|
+
revocationId: string;
|
|
18
|
+
fingerprint: string;
|
|
19
|
+
ip: string;
|
|
20
|
+
revokedAt: string;
|
|
21
|
+
revokedBy: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function readRevocationFiles(sourceURL: string): Promise<RevocationFile[]>;
|
|
25
|
+
/** Which key is allowed from which address. Unrevokes live in the source repo, so they are covered
|
|
26
|
+
by its signature, and nothing is read out of them but the pairs they allow and, for files
|
|
27
|
+
written before pairs existed, the revocation ids they name. */
|
|
28
|
+
export type Unrevokes = {
|
|
29
|
+
pairs: Map<string, string[]>;
|
|
30
|
+
legacyIds: Map<string, string[]>;
|
|
31
|
+
};
|
|
32
|
+
export declare function readUnrevokes(sourceURL: string): Promise<Unrevokes>;
|
|
33
|
+
/** Every unrevoke covering one revocation: by the pair it is about, and by its id for the files
|
|
34
|
+
written before pairs existed. */
|
|
35
|
+
export declare function unrevokesFor(unrevokes: Unrevokes, revocation: {
|
|
36
|
+
revocationId: string;
|
|
37
|
+
fingerprint: string;
|
|
38
|
+
ip: string;
|
|
39
|
+
}): string[];
|
|
40
|
+
/** Writes a revocation, unless this key is already revoked for this address. Checked twice:
|
|
41
|
+
against what this machine already knows, which needs no network, and again against the repo
|
|
42
|
+
after pulling it, so a flood of unknown keys cannot turn into a flood of commits.
|
|
43
|
+
|
|
44
|
+
Deduplication is on the pair. The same key from a second address is a second event and gets its
|
|
45
|
+
own revocation, because an unrevoke only ever forgives the pair it names. */
|
|
46
|
+
export declare function recordRevocation(config: {
|
|
47
|
+
sourceURL: string;
|
|
48
|
+
fingerprint: string;
|
|
49
|
+
keyLine: string;
|
|
50
|
+
attempt: Attempt;
|
|
51
|
+
hostLabel: string;
|
|
52
|
+
}): Promise<boolean>;
|
|
53
|
+
/** Takes everything the revoke repos list into local state. Once here a revocation never leaves,
|
|
54
|
+
even if the file is deleted: the key that writes revocations is on every server, so an attacker
|
|
55
|
+
holding it could otherwise erase the record that locked them out. */
|
|
56
|
+
export declare function absorbRevocations(sourceURLs: string[]): Promise<void>;
|
|
57
|
+
/** An unrevoke counts the moment it is seen.
|
|
58
|
+
|
|
59
|
+
There used to be an hour's wait before honouring one, against a signing key that had itself
|
|
60
|
+
been stolen. It was never worth it: an unrevoke has to be signed, so writing one already takes
|
|
61
|
+
the hardware key, and anyone holding that can sign a new authorized_keys naming whatever they
|
|
62
|
+
like - they have no reason to go near an unrevoke. Meanwhile the wait cost real access, and a
|
|
63
|
+
machine deployed after an incident would freeze keys it had never had a problem with, for an
|
|
64
|
+
hour, because it was seeing the unrevoke for the first time.
|
|
65
|
+
|
|
66
|
+
What still protects a stolen signing key is the 24 hours before a NEW signer is accepted. */
|
|
67
|
+
export declare function applyUnrevokes(sourceURLs: string[]): Promise<void>;
|
|
68
|
+
/** Every key held out of authorized_keys: one that has at least one revocation nothing has
|
|
69
|
+
forgiven. A key forgiven for one address is an ordinary key again, and revoking it from another
|
|
70
|
+
address puts it back in here. */
|
|
71
|
+
export declare function revokedFingerprints(): Set<string>;
|
|
72
|
+
/** Drops revoked keys from the merged set, and says so the first time a key actually disappears -
|
|
73
|
+
which is the thing worth knowing, rather than the mere existence of a revocation. */
|
|
74
|
+
export declare function removeRevokedKeys(keys: string[]): Promise<string[]>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type KeySession = {
|
|
2
|
+
processId: number;
|
|
3
|
+
user: string;
|
|
4
|
+
ip: string;
|
|
5
|
+
port: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function parseAcceptedSessions(contents: string, fingerprint: string): KeySession[];
|
|
8
|
+
/** Ends every live session that authenticated with this key. Returns what was ended, so the
|
|
9
|
+
revocation can say so rather than leaving it to be discovered. */
|
|
10
|
+
export declare function endSessionsUsingKey(fingerprint: string): Promise<KeySession[]>;
|
|
11
|
+
/** Disconnects every ssh session on the machine.
|
|
12
|
+
|
|
13
|
+
Not only the ones using the key that was just taken away: a session that predates the key being
|
|
14
|
+
removed is exactly as dangerous, and working out which sessions are still entitled to be here
|
|
15
|
+
is guesswork. Anyone who still has access can reconnect in a second, and anyone who does not
|
|
16
|
+
should not be here. */
|
|
17
|
+
export declare function endAllSSHSessions(): Promise<number[]>;
|
|
18
|
+
export declare function describeAllEnded(ended: number[]): string;
|
|
19
|
+
export declare function describeEndedSessions(ended: KeySession[]): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** What we last decided to trust for one source, kept on disk so nobody can tell us a different
|
|
2
|
+
story about what we saw last time. */
|
|
3
|
+
export type SourceState = {
|
|
4
|
+
lastSha: string;
|
|
5
|
+
branch: string;
|
|
6
|
+
accepted: boolean;
|
|
7
|
+
acceptedSigner: string;
|
|
8
|
+
acceptedKeys: string[];
|
|
9
|
+
acceptedManifestHash: string;
|
|
10
|
+
acceptedSignatureHash: string;
|
|
11
|
+
pendingSigner: string;
|
|
12
|
+
pendingSince: number;
|
|
13
|
+
reportedProblem: string;
|
|
14
|
+
};
|
|
15
|
+
/** One revocation event: this key, used from this address, so it is no longer accepted. Kept even
|
|
16
|
+
when the revoke repo no longer lists it: the deploy key that writes revocations lives on every
|
|
17
|
+
server, so an attacker who took one could otherwise delete the revocation that locked them out.
|
|
18
|
+
|
|
19
|
+
The id is unique to the event, never derived from the key. A key that has been forgiven for one
|
|
20
|
+
address is still an ordinary key, and using it from some other address revokes it again, with a
|
|
21
|
+
new id that no existing unrevoke names. */
|
|
22
|
+
export type RevocationState = {
|
|
23
|
+
revocationId: string;
|
|
24
|
+
fingerprint: string;
|
|
25
|
+
ip: string;
|
|
26
|
+
revokedAt: string;
|
|
27
|
+
revokedBy: string;
|
|
28
|
+
reason: string;
|
|
29
|
+
unrevokeId: string;
|
|
30
|
+
unrevoked: boolean;
|
|
31
|
+
reportedRemoved: boolean;
|
|
32
|
+
};
|
|
33
|
+
/** A refusal we saw but could not write down yet. Held until it is recorded, because the log is
|
|
34
|
+
read once and moves on: losing one of these to a repo that happened to be unreachable would
|
|
35
|
+
leave a key that was misused accepted forever. */
|
|
36
|
+
export type PendingRevocation = {
|
|
37
|
+
fingerprint: string;
|
|
38
|
+
keyLine: string;
|
|
39
|
+
sourceURL: string;
|
|
40
|
+
attempt: {
|
|
41
|
+
ip: string;
|
|
42
|
+
user: string;
|
|
43
|
+
port: string;
|
|
44
|
+
required: string;
|
|
45
|
+
line: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export type DaemonState = {
|
|
49
|
+
sources: {
|
|
50
|
+
[repoURL: string]: SourceState;
|
|
51
|
+
};
|
|
52
|
+
userKeyHashes: {
|
|
53
|
+
[userName: string]: string;
|
|
54
|
+
};
|
|
55
|
+
revocations: {
|
|
56
|
+
[revocationId: string]: RevocationState;
|
|
57
|
+
};
|
|
58
|
+
pendingRevocations: PendingRevocation[];
|
|
59
|
+
appliedKeys: string[];
|
|
60
|
+
authLogOffset: number;
|
|
61
|
+
authLogSignature: string;
|
|
62
|
+
};
|
|
63
|
+
export declare function getState(): DaemonState;
|
|
64
|
+
/** Per source progress, created on first use so a newly added source starts clean. */
|
|
65
|
+
export declare function sourceState(repoURL: string): SourceState;
|
|
66
|
+
export declare function loadState(): Promise<void>;
|
|
67
|
+
export declare function saveState(): Promise<void>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** The signature carries the public key that made it, so the key itself can be reported rather
|
|
2
|
+
than only a fingerprint. Returns it in the usual "type base64" form. */
|
|
3
|
+
export declare function publicKeyFromSignature(signatureText: string): {
|
|
4
|
+
publicKey: string;
|
|
5
|
+
fingerprint: string;
|
|
6
|
+
};
|
|
7
|
+
/** Reads one checkout's keys. Prefers a top level authorized_keys file and otherwise concatenates
|
|
8
|
+
every .pub at the top level. */
|
|
9
|
+
export declare function readCheckoutKeys(repoPath: string): Promise<string[]>;
|
|
10
|
+
/** Proves a checkout on disk was signed, and that the files in it are the ones that were signed.
|
|
11
|
+
Returns the signer, and throws if anything about that does not hold.
|
|
12
|
+
|
|
13
|
+
Anything reading a repo for something other than ssh keys goes through this first: the whole
|
|
14
|
+
checkout is covered by one manifest, so a machine list is exactly as trustworthy as the keys
|
|
15
|
+
beside it, and neither is worth reading unsigned. */
|
|
16
|
+
export declare function verifyCheckout(repoPath: string): Promise<string>;
|
|
17
|
+
export declare function describeSigner(signer: string): string;
|
|
18
|
+
/** The keys a source is allowed to contribute right now. A source signed by someone we have not
|
|
19
|
+
accepted keeps contributing the keys we last accepted, until the delay has passed. */
|
|
20
|
+
export declare function resolveSourceKeys(repoURL: string): Promise<string[]>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The keys repo to act on.
|
|
2
|
+
|
|
3
|
+
A named one if there is one, otherwise the repo the command is being run in if that repo holds
|
|
4
|
+
keys, otherwise this machine's own. Running in some other repo therefore reaches the keys repo
|
|
5
|
+
rather than acting on whatever happened to be around - signing sliftutils because that is where
|
|
6
|
+
the terminal was is never what anybody meant. */
|
|
7
|
+
export declare function resolveKeysRepo(named?: string): Promise<{
|
|
8
|
+
repoPath: string;
|
|
9
|
+
sourceURL: string;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const REVOKE_KEY_LABEL = "revokegithubkey";
|
|
2
|
+
export declare function revokeRepoURL(sourceURL: string): string;
|
|
3
|
+
export declare function revokeKeyPath(sourceURL: string): string;
|
|
4
|
+
export declare function legacyRevokeKeyPath(sourceURL: string): string;
|
|
5
|
+
/** Wherever this source's revoke key already is, or nothing if it has not been derived yet. */
|
|
6
|
+
export declare function findRevokeKey(sourceURL: string): Promise<string>;
|
|
7
|
+
export declare function revokeRepoPath(sourceURL: string): string;
|
|
8
|
+
/** The revoke repo's key, worked out from the source repo's key. Anything holding the source key
|
|
9
|
+
can produce it, so it never has to be stored anywhere separately or handed around. */
|
|
10
|
+
export declare function deriveRevokeKey(sourcePrivateKey: string): {
|
|
11
|
+
publicKey: string;
|
|
12
|
+
privateKeyFile: string;
|
|
13
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const KEYS_DIR_NAME = "authorized_keys";
|
|
2
|
+
export declare const LEGACY_REPO_KEYS_DIR = "/etc/portsecure/repo-keys";
|
|
3
|
+
export declare const REPOS_DIR = "/var/lib/portsecure/authorized-keys-repos";
|
|
4
|
+
export declare function keysDir(): string;
|
|
5
|
+
/** A repo url reduced to something usable as a file name. Derived rather than configured, so the
|
|
6
|
+
daemon and the deploy script always agree on where a source's key and checkout live. */
|
|
7
|
+
export declare function sourceName(repoURL: string): string;
|
|
8
|
+
export declare function sourceKeyPath(repoURL: string): string;
|
|
9
|
+
export declare function legacySourceKeyPath(repoURL: string): string;
|
|
10
|
+
export declare function sourceRepoPath(repoURL: string): string;
|
|
11
|
+
/** The key for a source, wherever it happens to be. The user folder is where they go now, and the
|
|
12
|
+
old location is still read so a host that predates the move keeps working. */
|
|
13
|
+
export declare function findKey(config: {
|
|
14
|
+
current: string;
|
|
15
|
+
legacy: string;
|
|
16
|
+
}): Promise<string>;
|
|
17
|
+
export declare function findSourceKey(repoURL: string): Promise<string>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Windows shells do not expand ~ themselves, and hunting down your home folder by hand is
|
|
2
|
+
annoying, so we expand it on every platform. Both separators are accepted, because a Windows
|
|
3
|
+
user may type either one. */
|
|
4
|
+
export declare function expandHome(filePath: string): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** An argument list rather than a shell string, so hostnames and paths can never be parsed as
|
|
2
|
+
shell syntax. Resolves with the exit code instead of throwing, callers decide what a failure
|
|
3
|
+
means. `inheritStderr` lets a child's own errors reach the terminal as they happen. */
|
|
4
|
+
export declare function spawnPromise(config: {
|
|
5
|
+
command: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
cwd?: string;
|
|
8
|
+
input?: string;
|
|
9
|
+
inheritStderr?: boolean;
|
|
10
|
+
timeoutTime?: number;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
stdout: string;
|
|
13
|
+
stderr: string;
|
|
14
|
+
status: number | undefined;
|
|
15
|
+
error: Error | undefined;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
/** A label mixed into a key's secret to get another key, so one key can stand behind several
|
|
4
|
+
identities without any of them being stored.
|
|
5
|
+
|
|
6
|
+
HMAC rather than hashing the two joined together: with a plain hash, anyone who obtained one
|
|
7
|
+
derived key could extend it into further labels without ever knowing the source key. The label
|
|
8
|
+
is the message and the source secret is the key, which is what HMAC is for. */
|
|
9
|
+
export declare function deriveEd25519Key(config: {
|
|
10
|
+
seed: Buffer;
|
|
11
|
+
label: string;
|
|
12
|
+
}): {
|
|
13
|
+
seed: Buffer;
|
|
14
|
+
publicKey: Buffer;
|
|
15
|
+
};
|
|
16
|
+
export declare function main(): Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
/** The public key of a seed, worked out by node rather than by reimplementing the curve. */
|
|
4
|
+
export declare function publicKeyFromSeed(seed: Buffer): Buffer;
|
|
5
|
+
export declare function parseOpenSSHPrivateKey(contents: string): {
|
|
6
|
+
seed: Buffer;
|
|
7
|
+
publicKey: Buffer;
|
|
8
|
+
comment: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function formatOpenSSHPrivateKey(config: {
|
|
11
|
+
seed: Buffer;
|
|
12
|
+
comment: string;
|
|
13
|
+
}): string;
|
|
14
|
+
/** The single line form, as it appears in authorized_keys and in a .pub file. */
|
|
15
|
+
export declare function formatPublicKeyLine(config: {
|
|
16
|
+
publicKey: Buffer;
|
|
17
|
+
comment: string;
|
|
18
|
+
}): string;
|