Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for multiple TUF repo caches #941

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: 3 additions & 0 deletions packages/client/src/__tests__/sigstore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import * as invalidBundles from './__fixtures__/bundles/invalid';
import * as validBundles from './__fixtures__/bundles/valid';
import { trustedRoot } from './__fixtures__/trust';

import path from 'path';
import type { SignOptions, VerifyOptions } from '../config';

const fulcioURL = 'https://fulcio.example.com';
Expand Down Expand Up @@ -129,6 +130,7 @@ describe('#verify', () => {
tufOptions = {
tufMirrorURL: tufRepo.baseURL,
tufCachePath: tufRepo.cachePath,
tufRootPath: path.join(tufRepo.cachePath, 'root.json'),
certificateIssuer: 'https://github.com/login/oauth',
};
});
Expand Down Expand Up @@ -243,6 +245,7 @@ describe('#createVerifier', () => {
tufOptions = {
tufMirrorURL: tufRepo.baseURL,
tufCachePath: tufRepo.cachePath,
tufRootPath: path.join(tufRepo.cachePath, 'root.json'),
};
});

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
Loading