Skip to content

Commit

Permalink
feat: export assertPolicy helper to check pkg version against the r…
Browse files Browse the repository at this point in the history
…ules preset
  • Loading branch information
antongolub committed Oct 28, 2023
1 parent 960ec1b commit 7644b50
Show file tree
Hide file tree
Showing 16 changed files with 158 additions and 95 deletions.
79 changes: 67 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,45 @@
# IDEs
.idea

# Credentials
**/*.asc
**/*.key
**/*.pem
**/*.cert
**/.npmrc
**/.yarnrc

# Bundles
bundle
build
buildstamp.json
dist
docs
flow-typed
lib
target/*
typings

# Fixtures
!src/test/fixtures/ts-project/target

# Temp assets
.temp
temp

# Codeclimate
codeclimate.*
cc-reporter
cc-reporter.*

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
Expand All @@ -20,6 +55,7 @@ lib-cov

# Coverage directory used by tools like istanbul
coverage
coverage.*
*.lcov

# nyc test coverage
Expand All @@ -41,18 +77,24 @@ build/Release
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo
.tsbuildinfo
buildcache
.buildcache

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
Expand All @@ -68,29 +110,41 @@ typings/
# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
# dotenv environment variable files
.env
.env.test
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

Expand All @@ -103,11 +157,12 @@ dist
# TernJS port file
.tern-port

# Temp testing lockfiles
yarn.lock
_.npmrc
.npmrc
temp
# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# IDE files
.idea
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ npm-registry-firewall /path/to/config.json

### JS API
```js
import {createApp} from 'npm-registry-firewall'
import {createApp, assertPolicy} from 'npm-registry-firewall'
const app = createApp({
server: {
Expand Down Expand Up @@ -162,6 +162,18 @@ const app = createApp({
})
await app.start()
// Checks the specified pkg version against the rules preset
await assertPolicy({
name: 'eventsource',
version: '1.1.0',
registry: 'https://registry.npmjs.org',
rules: [{
plugin: [['npm-registry-firewall/audit', {
critical: 'deny'
}]]
}]
}, 'allow') // Error: assert policy: deny !== allow
```

### TS libdefs
Expand Down
Binary file removed cc-reporter
Binary file not shown.
1 change: 0 additions & 1 deletion cc-reporter.sha256

This file was deleted.

Binary file removed cc-reporter.sha256.sig
Binary file not shown.
54 changes: 0 additions & 54 deletions public-key.asc

This file was deleted.

15 changes: 11 additions & 4 deletions src/main/js/firewall/engine/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@ export {
getPipeline,
}

export const assertPolicy = async ({org, name, version, rules, registry, authorization}) => {
const boundContext = await getBoundContext({org, name, version, rules, registry, authorization})
export const assertPolicy = async ({org, name, version, rules, registry, token}, _policy) => {
const boundContext = await getBoundContext({org, name, version, rules, registry, token})
const {directives} = await getAssets(boundContext)
const policy = getPolicy(directives, version)

if (_policy && _policy !== policy) {
throw new Error(`assert policy: ${policy} !== ${_policy}`)
}
return policy
}

export const getAssets = async (boundContext) => {
const {name, org, version, registry} = boundContext
const url = (org ? `${org}/` : '') + `${name}/-/${name}.tgz`
const {name, org, version, registry, rules} = boundContext
const url = `${name}/-/${name.slice(name.indexOf('/') + 1)}-${version}.tgz`
const [
{ packument, packumentBufferZip, headers, etag, deps, directives },
tarball
Expand Down
5 changes: 3 additions & 2 deletions src/main/js/firewall/engine/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ const getAuth = (token, auth) => token
:`Bearer ${token}`
: auth

export const getBoundContext = async ({org, name, version, rules, registry, token, req = {headers: {}}}) => {
export const getBoundContext = async ({name, version, rules, registry, token, entrypoint: _entrypoint, req = {headers: {}, base: ''}}) => {
const config = getConfig()
const org = name.charAt(0) === '@' ? name.slice(0, (name.indexOf('/') + 1 || name.indexOf('%') + 1) - 1) : null
const authorization = getAuth(token, req.headers['authorization'])
const entrypoint = _entrypoint || normalizePath(`${config.server.entrypoint}${base}`)
const entrypoint = _entrypoint || normalizePath(`${config?.server?.entrypoint || ''}${req.base}`)
const pipeline = await getPipeline(rules)

return { registry, entrypoint, authorization, name, org, version, pipeline, rules }
Expand Down
6 changes: 3 additions & 3 deletions src/main/js/firewall/engine/packument.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import crypto from 'node:crypto'
import {getDirectives, getPolicy} from './common.js'
import {request} from '../../http/index.js'
import {logger} from '../../logger.js'
import {asArray, tryQueue, time} from '../../util.js'
import {asArray, tryQueue, time, replaceAll} from '../../util.js'
import {withCache} from '../../cache.js'
import {semver} from '../../semver.js'
import {gunzip} from '../../zip.js'

export const getPackument = async ({boundContext, rules}) => {
export const getPackument = async ({boundContext, rules = boundContext.rules}) => {
const { registry, authorization, entrypoint, name } = boundContext
const {buffer, headers} = await withCache(`packument-${name}`, async () => {
const args = asArray(registry).map(r => [{
Expand Down Expand Up @@ -138,7 +138,7 @@ const logDenied = (name, directives) => {

if (Object.keys(denied).length > 0) {
const formatted = Object.entries(denied).reduce((m, [snap, versions]) =>
m + `${versions.join(',')} by ${snap.replaceAll('\"', '')} `
m + `${versions.join(',')} by ${replaceAll(snap, '\"', '')} `
, '')
logger.info(`denied ${name} versions: ${formatted}`)
}
Expand Down
22 changes: 7 additions & 15 deletions src/main/js/firewall/middleware.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {httpError, NOT_FOUND, ACCESS_DENIED, METHOD_NOT_ALLOWED, NOT_MODIFIED, OK, FOUND} from '../http/index.js'
import {getPolicy, getPipeline, checkTarball, getPackument} from './engine/api.js'
import {normalizePath, dropNullEntries, time, jsonBuffer} from '../util.js'
import {getPolicy, getPackument, getAssets} from './engine/api.js'
import {dropNullEntries, time, jsonBuffer} from '../util.js'
import {gzip} from '../zip.js'
import {hasHit, hasKey, isNoCache} from '../cache.js'
import {logger} from '../logger.js'
import {getConfig} from '../config.js'
import {getBoundContext} from "./engine/common.js";

const warmupPipeline = (pipeline, opts, warmup = getConfig().warmup) => {
if (warmup <= 0 || isNoCache()) return
Expand Down Expand Up @@ -46,28 +47,19 @@ const getAuth = (token, auth) => token
:`Bearer ${token}`
: auth

export const firewall = ({registry, rules, entrypoint: _entrypoint, token}) => async (req, res, next) => {
export const firewall = ({registry, rules, entrypoint, token}) => async (req, res, next) => {
const {routeParams: {name, version, org}, base, method} = req
req.timed = true

if (method !== 'GET' && method !== 'HEAD') {
return next(httpError(METHOD_NOT_ALLOWED))
}

const config = getConfig()
const authorization = getAuth(token, req.headers['authorization'])
const entrypoint = _entrypoint || normalizePath(`${config.server.entrypoint}${base}`)
const pipeline = await getPipeline(rules)
const boundContext = { registry, entrypoint, authorization, name, org, version, pipeline }
const boundContext = await getBoundContext({org, name, version, rules, registry, token, entrypoint, req})
const {pipeline} = boundContext

warmupPipeline(pipeline, boundContext)
const [
{ packument, packumentBufferZip, headers, etag, deps, directives },
tarball
] = await Promise.all([
getPackument({ boundContext, rules }),
version ? checkTarball({registry, url: req.url}) : Promise.resolve(false)
])
const {packument, packumentBufferZip, headers, etag, deps, directives, tarball} = await getAssets(boundContext)

if (!packument) {
return next(httpError(NOT_FOUND))
Expand Down
4 changes: 2 additions & 2 deletions src/main/js/http/router.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {asArray, normalizePath, once} from '../util.js'
import {asArray, normalizePath, once, replaceAll} from '../util.js'

const normalizeRoute = (item) => {
const [m, p, cb] = asArray(item)
Expand Down Expand Up @@ -35,7 +35,7 @@ const getRouteParams = (url, pattern, rmap) => rmap && pattern instanceof RegExp
.reduce((m, v, k) => {
const _k = rmap[k]
if (_k) {
m[_k] = v?.replace('%2f', '/')
m[_k] = replaceAll(v, '%2f', '/')
}
return m
}, {})
Expand Down
8 changes: 8 additions & 0 deletions src/main/js/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,11 @@ export function createLogger(options: TLoggerOptions): TLogger
export function getPercentiles(name: string, percentiles: number[]): number[]

export function getMetricsDigest(): Record<string, any>

export function assertPolicy(opts: {
name: string
version: string
rules: TRule | TRule[]
registry: string
token?: string
}, policy?: TPolicy): TPolicy
1 change: 1 addition & 0 deletions src/main/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './logger.js'
export * from './als.js'
export * from './config.js'
export { getPercentiles, getMetricsDigest } from './metric.js'
export { assertPolicy } from './firewall/engine/api.js'
8 changes: 8 additions & 0 deletions src/main/js/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,11 @@ export const fromArrayBufferToBuffer = (ab) => {
}
return buf
}

export const replaceAll = (a, b, c) => {
if (!a) return a

return a.replaceAll
? a.replaceAll(b, c)
: a.split(b).join(c)
}
Loading

0 comments on commit 7644b50

Please sign in to comment.