-
Notifications
You must be signed in to change notification settings - Fork 65
/
policyGenerator.js
133 lines (127 loc) · 4.75 KB
/
policyGenerator.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
const {
createModuleInspector,
LavamoatModuleRecord,
loadPoliciesSync,
// @ts-expect-error - missing types
} = require('lavamoat-core')
const { getPackageNameForModulePath } = require('@lavamoat/aa')
const { writeFileSync, mkdirSync } = require('node:fs')
const path = require('node:path')
const stringify = require('json-stable-stringify')
const {
sources: { RawSource },
} = require('webpack')
const POLICY_SNAPSHOT_FILENAME = 'policy-snapshot.json'
module.exports = {
/**
* @param {Object} opts
* @param {import('../types.js').Policy} [opts.policyFromOptions] - The
* hardcoded policy passed in options, takes precedence over reading from
* files
* @param {import('@lavamoat/aa').CanonicalNameMap} opts.canonicalNameMap -
* Generated from aa
* @param {import('webpack').Compilation} opts.compilation - Webpack
* compilation reference (for emitting assets)
* @param {boolean} opts.enabled - Whether to generate a policy
* @param {string} opts.location - Where to read/write the policy files
* @param {boolean} [opts.emit] - Whether to emit the policy snapshot as an
* asset
* @returns
*/
createPolicyGenerator({
policyFromOptions,
canonicalNameMap,
compilation,
enabled,
location,
emit = false,
}) {
const { policy, applyOverride } = loadPoliciesSync({
policyPath: path.join(location, 'policy.json'),
policyOverridePath: path.join(location, 'policy-override.json'),
debugMode: false,
})
if (!enabled)
return {
inspectWebpackModule: () => {},
getPolicy: () => {
let final
if (policyFromOptions) {
// TODO: avoid loading the policy file if policyFromOptions is present
final = policyFromOptions
} else {
final = applyOverride(policy)
}
if (emit) {
compilation.emitAsset(
POLICY_SNAPSHOT_FILENAME,
new RawSource(stringify(final, { space: 2 }))
)
}
return final
},
}
// load policy file
// load overrides
// generate policy if requested and write to file
// merge result with overrides
// return that and emit snapshot
const moduleInspector = createModuleInspector({
isBuiltin: () => false,
includeDebugInfo: false,
// If the specifier is requested as a dependency in importMap but was never passed to inspectModule, its package name will be looked up here.
// This is a workaround to inconsistencies in how webpack represents connections. We're not aware of any security implications of this, since the package is already resolved clearly and this is only a part of policy generation, not runtime.
moduleToPackageFallback: (specifier) =>
getPackageNameForModulePath(canonicalNameMap, specifier),
})
return {
/**
* @param {import('webpack').NormalModule} module
* @param {Iterable<import('webpack').ModuleGraphConnection>} connections
*/
inspectWebpackModule: (module, connections) => {
// process._rawDebug(module.originalSource().source())
const moduleRecord = new LavamoatModuleRecord({
// Knowing the actual specifier is not relevant here, they're used as unique identifiers that match between here and dependencies
specifier: module.userRequest,
file: module.userRequest,
type: 'js',
packageName: getPackageNameForModulePath(
canonicalNameMap,
module.userRequest
),
content: module.originalSource()?.source()?.toString(),
importMap: {
// connections are a much better source of information than module.dependencies which contain
// all imported references separately along with exports and fluff
...Array.from(connections).reduce((acc, dep) => {
// @ts-expect-error - bad types?
const depSpecifier = dep.resolvedModule.userRequest
acc[depSpecifier] = depSpecifier
return acc
}, /** @type {Record<string, string>} */ ({})),
},
//ast: module._ast, - would have to translate to babel anyway
})
moduleInspector.inspectModule(moduleRecord)
},
getPolicy: () => {
const policy = moduleInspector.generatePolicy({})
mkdirSync(location, { recursive: true })
writeFileSync(
path.join(location, 'policy.json'),
stringify(policy, { space: 2 }),
'utf8'
)
const final = applyOverride(policy)
if (emit) {
compilation.emitAsset(
POLICY_SNAPSHOT_FILENAME,
new RawSource(stringify(final, { space: 2 }))
)
}
return final
},
}
},
}