Skip to content

Commit

Permalink
support for multiple TUF repo caches
Browse files Browse the repository at this point in the history
Signed-off-by: Brian DeHamer <bdehamer@github.com>
  • Loading branch information
bdehamer committed Jan 10, 2024
1 parent 414f8d0 commit e54ff56
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 104 deletions.
5 changes: 5 additions & 0 deletions .changeset/sharp-rabbits-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sigstore/tuf": minor
---

Add support for caching metadata from multiple TUF repositories
8 changes: 4 additions & 4 deletions .github/workflows/smoke-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,6 @@ jobs:
- name: Retrieve TUF trusted root
run: |
wget "${SIGSTORE_URL}/1.root.json"
- name: Initialize local TUF cache
run: |
./packages/cli/bin/run init --mirror ${SIGSTORE_URL} --root ./1.root.json --force
- name: Create artifact to sign
run: |
echo -n "hello world" > artifact
Expand All @@ -91,7 +88,10 @@ jobs:
artifact
- name: Verify bundle
run: |
./packages/cli/bin/run verify bundle.json
./packages/cli/bin/run verify \
--tuf-mirror-url ${SIGSTORE_URL} \
--tuf-root-path ./1.root.json \
bundle.json
- name: Archive bundle
if: success() || failure()
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v3
Expand Down
3 changes: 1 addition & 2 deletions packages/tuf/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
"test": "jest"
},
"files": [
"dist",
"store"
"dist"
],
"author": "bdehamer@github.com",
"license": "Apache-2.0",
Expand Down
94 changes: 56 additions & 38 deletions packages/tuf/src/__tests__/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,51 @@ import os from 'os';
import path from 'path';
import { TUFClient, TUFOptions } from '../client';
import { TUFError } from '../error';
import { REPO_SEEDS } from '../store';

describe('TUFClient', () => {
const rootPath = require.resolve(
'../../store/public-good-instance-root.json'
);

describe('constructor', () => {
const cacheDir = path.join(os.tmpdir(), 'tuf-client-test');
const mirrorURL = 'https://example.com';
let rootSeedDir: string;
let rootPath: string;

const repoName = 'example.com';
const mirrorURL = `https://${repoName}`;
const cacheRoot = path.join(os.tmpdir(), 'tuf-client-test');
const cacheDir = path.join(cacheRoot, repoName);
const force = false;
afterEach(() => fs.rmSync(cacheDir, { recursive: true }));

beforeEach(() => {
rootSeedDir = fs.mkdtempSync(
path.join(os.tmpdir(), 'tuf-client-test-seed')
);

rootPath = path.join(rootSeedDir, 'root-seed.json');
fs.writeFileSync(
rootPath,
Buffer.from(
REPO_SEEDS['https://tuf-repo-cdn.sigstore.dev']['root.json'],
'base64'
)
);
});

afterEach(() => {
fs.rmSync(cacheRoot, { recursive: true });
fs.rmSync(rootSeedDir, { recursive: true });
});

describe('when the cache directory does not exist', () => {
it('creates the cache directory', () => {
new TUFClient({ cachePath: cacheDir, mirrorURL, rootPath, force });
new TUFClient({ cachePath: cacheRoot, mirrorURL, rootPath, force });
expect(fs.existsSync(cacheDir)).toEqual(true);
expect(fs.existsSync(path.join(cacheDir, 'root.json'))).toEqual(true);
expect(fs.existsSync(path.join(cacheDir, 'remote.json'))).toEqual(true);
});
});

describe('when the cache directory already exists', () => {
beforeEach(() => {
fs.mkdirSync(cacheDir, { recursive: true });
fs.copyFileSync(rootPath, path.join(cacheDir, 'root.json'));
fs.writeFileSync(
path.join(cacheDir, 'remote.json'),
JSON.stringify({ mirror: mirrorURL })
);
});

it('loads config from the existing directory without error', () => {
Expand All @@ -59,45 +75,47 @@ describe('TUFClient', () => {
});
});

describe('when no explicit root.json is provided', () => {
describe('when the mirror URL does NOT match one of the embedded roots', () => {
const mirrorURL = 'https://oops.net';
it('throws an error', () => {
expect(
() => new TUFClient({ cachePath: cacheDir, mirrorURL, force })
).toThrowWithCode(TUFError, 'TUF_INIT_CACHE_ERROR');
});
});

describe('when the mirror URL matches one of the embedded roots', () => {
const mirrorURL = 'https://tuf-repo-cdn.sigstore.dev';
it('loads the embedded root.json', () => {
expect(
() => new TUFClient({ cachePath: cacheDir, mirrorURL, force })
).not.toThrow();
});
});
});

describe('when forcing re-initialization of an existing directory', () => {
const oldMirrorURL = mirrorURL;
const newMirrorURL = 'https://new.org';
const force = true;

beforeEach(() => {
fs.mkdirSync(cacheDir, { recursive: true });
fs.copyFileSync(rootPath, path.join(cacheDir, 'root.json'));
fs.writeFileSync(
path.join(cacheDir, 'remote.json'),
JSON.stringify({ mirror: oldMirrorURL })
);
fs.writeFileSync(path.join(cacheDir, 'root.json'), 'oops');
});

it('initializes the client without error', () => {
expect(
() =>
new TUFClient({
cachePath: cacheDir,
mirrorURL: newMirrorURL,
rootPath,
force,
})
new TUFClient({ cachePath: cacheRoot, mirrorURL, rootPath, force })
).not.toThrow();
});

it('overwrites the existing values', () => {
new TUFClient({
cachePath: cacheDir,
mirrorURL: newMirrorURL,
rootPath,
force,
});
new TUFClient({ cachePath: cacheRoot, mirrorURL, rootPath, force });

const remote = fs.readFileSync(
path.join(cacheDir, 'remote.json'),
'utf-8'
);
expect(JSON.parse(remote)).toEqual({ mirror: newMirrorURL });
const root = fs.readFileSync(path.join(cacheDir, 'root.json'), 'utf-8');
expect(root).toBeDefined();
expect(root).not.toEqual('oops');
});
});
});
Expand All @@ -117,7 +135,7 @@ describe('TUFClient', () => {
mirrorURL: tufRepo.baseURL,
cachePath: tufRepo.cachePath,
retry: false,
rootPath,
rootPath: path.join(tufRepo.cachePath, 'root.json'),
force: false,
};
});
Expand Down
16 changes: 8 additions & 8 deletions packages/tuf/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
import { TrustedRoot } from '@sigstore/protobuf-specs';
import mocktuf, { Target } from '@tufjs/repo-mock';
import fs from 'fs';
import path from 'path';
import { TUF, TUFError, TUFOptions, getTrustedRoot, initTUF } from '..';
import { TUFClient } from '../client';

Expand Down Expand Up @@ -73,7 +74,7 @@ describe('getTrustedRoot', () => {
mirrorURL: tufRepo.baseURL,
cachePath: tufRepo.cachePath,
retry: false,
rootPath: 'n/a',
rootPath: path.join(tufRepo.cachePath, 'root.json'),
};
});

Expand All @@ -96,27 +97,26 @@ describe('initTUF', () => {
mirrorURL: tufRepo.baseURL,
cachePath: tufRepo.cachePath,
retry: false,
rootPath: 'n/a',
rootPath: path.join(tufRepo.cachePath, 'root.json'),
};
});

afterEach(() => tufRepo?.teardown());

it('returns a TUFCLient', async () => {
it('returns a TUFClient', async () => {
const tuf = await initTUF(options);
expect(tuf).toBeInstanceOf(TUFClient);
});

it('sets-up the local TUF cache', async () => {
await initTUF(options);

const cachePath = tufRepo!.cachePath;
const cachePath = path.join(
tufRepo!.cachePath,
new URL(options!.mirrorURL!).host
);
expect(fs.existsSync(cachePath)).toBe(true);
expect(fs.existsSync(`${cachePath}/remote.json`)).toBe(true);
expect(fs.existsSync(`${cachePath}/root.json`)).toBe(true);
expect(fs.existsSync(`${cachePath}/snapshot.json`)).toBe(true);
expect(fs.existsSync(`${cachePath}/timestamp.json`)).toBe(true);
expect(fs.existsSync(`${cachePath}/targets.json`)).toBe(true);
expect(fs.existsSync(`${cachePath}/targets`)).toBe(true);
});
});
Loading

0 comments on commit e54ff56

Please sign in to comment.