feat(core): throw an error when unknown local cache source
This commit is contained in:
parent
95a2256a34
commit
1bc58c997d
@ -3291,6 +3291,14 @@
|
|||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
"children": [],
|
"children": [],
|
||||||
"disableCollapsible": false
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Unknown Local Cache Error",
|
||||||
|
"path": "/recipes/other/unknown-local-cache",
|
||||||
|
"id": "unknown-local-cache",
|
||||||
|
"isExternal": false,
|
||||||
|
"children": [],
|
||||||
|
"disableCollapsible": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"disableCollapsible": false
|
"disableCollapsible": false
|
||||||
@ -3494,6 +3502,14 @@
|
|||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
"children": [],
|
"children": [],
|
||||||
"disableCollapsible": false
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Unknown Local Cache Error",
|
||||||
|
"path": "/recipes/other/unknown-local-cache",
|
||||||
|
"id": "unknown-local-cache",
|
||||||
|
"isExternal": false,
|
||||||
|
"children": [],
|
||||||
|
"disableCollapsible": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1597,6 +1597,16 @@
|
|||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
"path": "/recipes/other/standalone-ngrx-apis",
|
"path": "/recipes/other/standalone-ngrx-apis",
|
||||||
"tags": []
|
"tags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "unknown-local-cache",
|
||||||
|
"name": "Unknown Local Cache Error",
|
||||||
|
"description": "",
|
||||||
|
"file": "shared/guides/unknown-local-cache",
|
||||||
|
"itemList": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"path": "/recipes/other/unknown-local-cache",
|
||||||
|
"tags": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
@ -1852,5 +1862,15 @@
|
|||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
"path": "/recipes/other/standalone-ngrx-apis",
|
"path": "/recipes/other/standalone-ngrx-apis",
|
||||||
"tags": []
|
"tags": []
|
||||||
|
},
|
||||||
|
"/recipes/other/unknown-local-cache": {
|
||||||
|
"id": "unknown-local-cache",
|
||||||
|
"name": "Unknown Local Cache Error",
|
||||||
|
"description": "",
|
||||||
|
"file": "shared/guides/unknown-local-cache",
|
||||||
|
"itemList": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"path": "/recipes/other/unknown-local-cache",
|
||||||
|
"tags": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1458,6 +1458,12 @@
|
|||||||
"id": "standalone-ngrx-apis",
|
"id": "standalone-ngrx-apis",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"file": "shared/recipes/standalone-ngrx-apis"
|
"file": "shared/recipes/standalone-ngrx-apis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Unknown Local Cache Error",
|
||||||
|
"id": "unknown-local-cache",
|
||||||
|
"tags": [],
|
||||||
|
"file": "shared/guides/unknown-local-cache"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
86
docs/shared/guides/unknown-local-cache.md
Normal file
86
docs/shared/guides/unknown-local-cache.md
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# Unknown Local Cache Error
|
||||||
|
|
||||||
|
This document will explain why the following error happens and how to address it.
|
||||||
|
|
||||||
|
```
|
||||||
|
NX Invalid Cache Directory for Task "myapp:build"
|
||||||
|
|
||||||
|
The local cache artifact in "node_modules/.cache/nx/786524780459028195" was not been generated on this machine.
|
||||||
|
As a result, the cache's content integrity cannot be confirmed, which may make cache restoration potentially unsafe.
|
||||||
|
If your machine ID has changed since the artifact was cached, run "nx reset" to fix this issue.
|
||||||
|
Read about the error and how to address it here: https://nx.dev/recipes/other/unknown-local-cache
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nx Tracks Cache Source
|
||||||
|
|
||||||
|
Nx can cache tasks, which can drastically speed up your CI and local builds. However, this comes with the potential risk
|
||||||
|
of "cache poisoning", where cache artifacts could be intentionally or inadvertently overwritten. If another user
|
||||||
|
executes a task that matches the hash of the tainted artifact, they could retrieve the corrupted artifact and use it as
|
||||||
|
the outcome of the task. Nx and Nx Cloud contain several safeguards to minimize the likelihood of cache poisoning or, in
|
||||||
|
the case of Nx Cloud, completely prevent it.
|
||||||
|
|
||||||
|
The error above is one such safeguard.
|
||||||
|
|
||||||
|
Nx trusts the local cache. If you executed a task and stored the corresponding cached artifact on your machine, you can
|
||||||
|
safely restore it on the same machine without worrying about cache poisoning. After all, in order to tamper with the
|
||||||
|
cache artifact, the actor would need access to the machine itself.
|
||||||
|
|
||||||
|
However, when artifacts in the local cache are created by a different machine, we cannot make such assumption. By
|
||||||
|
default, Nx will refuse to use such artifacts and will throw the "Invalid Cache Directory" error.
|
||||||
|
|
||||||
|
## Your MachineId Has Changed
|
||||||
|
|
||||||
|
Upgrading your computer's hardware may alter its Machine ID, yielding the error above. To fix it execute `nx reset` to
|
||||||
|
remove all the cache directories created under the previous Machine ID. After doing so, you should no longer see the
|
||||||
|
error. After doing so, you should no longer see the error.
|
||||||
|
|
||||||
|
## You Share Cache with Another Machine Using a Network Drive
|
||||||
|
|
||||||
|
You can prefix any Nx command with `NX_REJECT_UNKNOWN_LOCAL_CACHE=0` to ignore the error (
|
||||||
|
e.g., `NX REJECT_UNKNOWN_LOCAL_CACHE=0 nx run-many -t build test`). This is similar to
|
||||||
|
setting `NODE_TLS_REJECT_UNAUTHORIZED=0` to ignore any errors stemming form self-signed certificates. Even though it
|
||||||
|
will make it work, this approach is discouraged.
|
||||||
|
|
||||||
|
Storing Nx's local cache on a network drive can present security risks. When a network drive is shared, every CI run has
|
||||||
|
access to all the previously created Nx cache artifacts. Hence, it is plausible for every single artifact - for every
|
||||||
|
single task hash - to be accessed without leaving any trace. This is feasible due to the network drive's capability to
|
||||||
|
allow overwrites.
|
||||||
|
|
||||||
|
Instead of sharing the network drive, we highly recommend you to implement the `RemoteCache` interface.
|
||||||
|
|
||||||
|
## Implementing Remote Cache Interface
|
||||||
|
|
||||||
|
This is the interface:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface RemoteCache {
|
||||||
|
retrieve(hash: string, cachePath: string);
|
||||||
|
|
||||||
|
store(hash: string, cachePath: string);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> You will need to wrap the default tasks runner to provide the remote cache implementation.
|
||||||
|
|
||||||
|
## How Nx Cloud Makes Sure Sharing Cache is Safe
|
||||||
|
|
||||||
|
The Nx Cloud runner provides an implementation of `RemoteCache` which does the following things making sharing the cache safe:
|
||||||
|
|
||||||
|
1. **Immutable Artifacts:** Nx Cloud allows you to create and store new artifacts without the ability to override the
|
||||||
|
existing ones. This prevents any possibility of poisoning an existing artifact. This is achieved by managing the
|
||||||
|
cache using short-lived signed URLs.
|
||||||
|
|
||||||
|
2. **Artifact Accessibility:** Nx Cloud provides access to the cache artifact specifically for the task that is
|
||||||
|
currently being executed. It restricts the ability to list all cache artifacts.
|
||||||
|
|
||||||
|
3. **Visibility Control:** Nx Cloud comes with options to manage the visibility of your cache artifacts. For instance,
|
||||||
|
the cache artifacts created in `main` might be accessible by anyone across any branch, whereas the artifacts created
|
||||||
|
in your PR could be shared only within your PR runs.
|
||||||
|
|
||||||
|
4. **Access Token Traceability:** Nx Cloud keeps a record of the access token used to create a cache artifact. In case
|
||||||
|
an access token gets compromised it can be easily removed, in turn deleting all the cache artifacts that were created
|
||||||
|
using it.
|
||||||
|
|
||||||
|
Nx Cloud is not the only remote cache you can use. If you are using a different remote cache or using your
|
||||||
|
own implementation, we would highly recommend ensuring that the same safety mechanisms as Nx Cloud have been put in
|
||||||
|
place.
|
||||||
@ -219,6 +219,7 @@
|
|||||||
- [Identify Dependencies Between Folders](/recipes/other/identify-dependencies-between-folders)
|
- [Identify Dependencies Between Folders](/recipes/other/identify-dependencies-between-folders)
|
||||||
- [Rescope Packages from @nrwl to @nx](/recipes/other/rescope)
|
- [Rescope Packages from @nrwl to @nx](/recipes/other/rescope)
|
||||||
- [Standalone NgRx APIs](/recipes/other/standalone-ngrx-apis)
|
- [Standalone NgRx APIs](/recipes/other/standalone-ngrx-apis)
|
||||||
|
- [Unknown Local Cache Error](/recipes/other/unknown-local-cache)
|
||||||
|
|
||||||
- Plugins
|
- Plugins
|
||||||
|
|
||||||
|
|||||||
@ -330,7 +330,8 @@
|
|||||||
"tailwindcss": "3.2.4",
|
"tailwindcss": "3.2.4",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"vitest": "^0.32.0",
|
"vitest": "^0.32.0",
|
||||||
"weak-napi": "^2.0.2"
|
"weak-napi": "^2.0.2",
|
||||||
|
"node-machine-id": "1.1.12"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"minimist": "^1.2.6",
|
"minimist": "^1.2.6",
|
||||||
|
|||||||
@ -64,7 +64,8 @@
|
|||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"v8-compile-cache": "2.3.0",
|
"v8-compile-cache": "2.3.0",
|
||||||
"yargs": "^17.6.2",
|
"yargs": "^17.6.2",
|
||||||
"yargs-parser": "21.1.1"
|
"yargs-parser": "21.1.1",
|
||||||
|
"node-machine-id": "1.1.12"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@swc-node/register": "^1.4.2",
|
"@swc-node/register": "^1.4.2",
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { DefaultTasksRunnerOptions } from './default-tasks-runner';
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { cacheDir } from '../utils/cache-directory';
|
import { cacheDir } from '../utils/cache-directory';
|
||||||
import { Task } from '../config/task-graph';
|
import { Task } from '../config/task-graph';
|
||||||
|
import { machineId } from 'node-machine-id';
|
||||||
|
|
||||||
export type CachedResult = {
|
export type CachedResult = {
|
||||||
terminalOutput: string;
|
terminalOutput: string;
|
||||||
@ -20,6 +21,8 @@ export class Cache {
|
|||||||
cachePath = this.createCacheDir();
|
cachePath = this.createCacheDir();
|
||||||
terminalOutputsDir = this.createTerminalOutputsDir();
|
terminalOutputsDir = this.createTerminalOutputsDir();
|
||||||
|
|
||||||
|
private _currentMachineId: string = null;
|
||||||
|
|
||||||
constructor(private readonly options: DefaultTasksRunnerOptions) {}
|
constructor(private readonly options: DefaultTasksRunnerOptions) {}
|
||||||
|
|
||||||
removeOldCacheRecords() {
|
removeOldCacheRecords() {
|
||||||
@ -44,6 +47,20 @@ export class Cache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async currentMachineId() {
|
||||||
|
if (!this._currentMachineId) {
|
||||||
|
try {
|
||||||
|
this._currentMachineId = await machineId();
|
||||||
|
} catch (e) {
|
||||||
|
if (process.env.NX_VERBOSE_LOGGING == 'true') {
|
||||||
|
console.log(`Unable to get machineId. Error: ${e.message}`);
|
||||||
|
}
|
||||||
|
this._currentMachineId = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._currentMachineId;
|
||||||
|
}
|
||||||
|
|
||||||
async get(task: Task): Promise<CachedResult | null> {
|
async get(task: Task): Promise<CachedResult | null> {
|
||||||
const res = await this.getFromLocalDir(task);
|
const res = await this.getFromLocalDir(task);
|
||||||
|
|
||||||
@ -98,6 +115,7 @@ export class Cache {
|
|||||||
// so if the process gets terminated while we are copying stuff into cache,
|
// so if the process gets terminated while we are copying stuff into cache,
|
||||||
// the cache entry won't be used.
|
// the cache entry won't be used.
|
||||||
await writeFile(join(td, 'code'), code.toString());
|
await writeFile(join(td, 'code'), code.toString());
|
||||||
|
await writeFile(join(td, 'source'), await this.currentMachineId());
|
||||||
await writeFile(tdCommit, 'true');
|
await writeFile(tdCommit, 'true');
|
||||||
|
|
||||||
if (this.options.remoteCache) {
|
if (this.options.remoteCache) {
|
||||||
@ -208,6 +226,32 @@ export class Cache {
|
|||||||
try {
|
try {
|
||||||
code = Number(await readFile(join(td, 'code'), 'utf-8'));
|
code = Number(await readFile(join(td, 'code'), 'utf-8'));
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
|
let sourceMachineId = null;
|
||||||
|
try {
|
||||||
|
sourceMachineId = await readFile(join(td, 'source'), 'utf-8');
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (
|
||||||
|
sourceMachineId &&
|
||||||
|
sourceMachineId != (await this.currentMachineId())
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
process.env.NX_REJECT_UNKNOWN_LOCAL_CACHE != '0' &&
|
||||||
|
process.env.NX_REJECT_UNKNOWN_LOCAL_CACHE != 'false'
|
||||||
|
) {
|
||||||
|
const error = [
|
||||||
|
`Invalid Cache Directory for Task "${task.id}"`,
|
||||||
|
`The local cache artifact in "${td}" was not been generated on this machine.`,
|
||||||
|
`As a result, the cache's content integrity cannot be confirmed, which may make cache restoration potentially unsafe.`,
|
||||||
|
`If your machine ID has changed since the artifact was cached, run "nx reset" to fix this issue.`,
|
||||||
|
`Read about the error and how to address it here: https://nx.dev/recipes/other/unknown-local-cache`,
|
||||||
|
``,
|
||||||
|
].join('\n');
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
terminalOutput,
|
terminalOutput,
|
||||||
outputsPath: join(td, 'outputs'),
|
outputsPath: join(td, 'outputs'),
|
||||||
|
|||||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@ -101,6 +101,9 @@ dependencies:
|
|||||||
next-seo:
|
next-seo:
|
||||||
specifier: ^5.13.0
|
specifier: ^5.13.0
|
||||||
version: 5.13.0(next@13.3.4)(react-dom@18.2.0)(react@18.2.0)
|
version: 5.13.0(next@13.3.4)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
node-machine-id:
|
||||||
|
specifier: 1.1.12
|
||||||
|
version: 1.1.12
|
||||||
npm-run-path:
|
npm-run-path:
|
||||||
specifier: ^4.0.1
|
specifier: ^4.0.1
|
||||||
version: 4.0.1
|
version: 4.0.1
|
||||||
@ -20359,7 +20362,6 @@ packages:
|
|||||||
|
|
||||||
/node-machine-id@1.1.12:
|
/node-machine-id@1.1.12:
|
||||||
resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==}
|
resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/node-releases@2.0.10:
|
/node-releases@2.0.10:
|
||||||
resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
|
resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user