Skip to content

Commit

Permalink
tuf store interface plus basic file system implementation (#160)
Browse files Browse the repository at this point in the history
Signed-off-by: Patrick Flynn <patrick@chainguard.dev>

Signed-off-by: Patrick Flynn <patrick@chainguard.dev>
  • Loading branch information
Patrick Flynn authored Sep 21, 2022
1 parent ec547f2 commit 9784bd9
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@
import static dev.sigstore.json.GsonSupplier.GSON;

import com.google.common.annotations.VisibleForTesting;
import dev.sigstore.tuf.model.Role;
import dev.sigstore.tuf.model.Root;
import dev.sigstore.tuf.model.SignedTufMeta;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import javax.annotation.Nullable;

/** Uses a local file system directory to store the trusted TUF metadata. */
public class FileSystemTufStore implements TufLocalStore {
Expand All @@ -34,44 +35,50 @@ public class FileSystemTufStore implements TufLocalStore {
private static final String SNAPSHOT_FILE_NAME = "snapshot.json";
private static final String TIMESTAMP_FILE_NAME = "timestamp.json";
private Path repoBaseDir;
private Root trustedRoot;

@VisibleForTesting
FileSystemTufStore(Path repoBaseDir, @Nullable Root trustedRoot) {
FileSystemTufStore(Path repoBaseDir) {
this.repoBaseDir = repoBaseDir;
this.trustedRoot = trustedRoot;
}

static TufLocalStore newFileSystemStore(Path repoBaseDir) throws IOException {
Path rootFile = repoBaseDir.resolve(ROOT_FILE_NAME);
Root trustedRoot = null;
if (rootFile.toFile().exists()) {
trustedRoot = GSON.get().fromJson(Files.readString(rootFile), Root.class);
static TufLocalStore newFileSystemStore(Path repoBaseDir) {
if (!repoBaseDir.toFile().isDirectory()) {
throw new IllegalArgumentException(repoBaseDir + " must be a file system directory.");
}
return new FileSystemTufStore(repoBaseDir, trustedRoot);
return new FileSystemTufStore(repoBaseDir);
}

@Override
public Optional<Root> getTrustedRoot() {
return Optional.ofNullable(trustedRoot);
public Optional<Root> loadTrustedRoot() throws IOException {
return loadRole(Role.Name.ROOT, Root.class);
}

@Override
public void setTrustedRoot(Root root) throws IOException {
if (root == null) {
throw new NullPointerException("Root should not be null");
<T extends SignedTufMeta> Optional<T> loadRole(Role.Name roleName, Class<T> tClass)
throws IOException {
Path roleFile = repoBaseDir.resolve(roleName + ".json");
if (!roleFile.toFile().exists()) {
return Optional.empty();
}
Path rootPath = repoBaseDir.resolve(ROOT_FILE_NAME);
if (trustedRoot != null) {
// back it up
Files.move(
rootPath,
repoBaseDir.resolve(trustedRoot.getSignedMeta().getVersion() + "." + ROOT_FILE_NAME));
return Optional.of(GSON.get().fromJson(Files.readString(roleFile), tClass));
}

<T extends SignedTufMeta> void saveRole(T role) throws IOException {
try (FileWriter fileWriter =
new FileWriter(repoBaseDir.resolve(role.getSignedMeta().getType() + ".json").toFile())) {
fileWriter.write(GSON.get().toJson(role));
}
trustedRoot = root;
try (FileWriter fileWriter = new FileWriter(rootPath.toFile())) {
fileWriter.write(GSON.get().toJson(trustedRoot));
}

@Override
public void storeTrustedRoot(Root root) throws IOException {
Optional<Root> trustedRoot = loadTrustedRoot();
if (trustedRoot.isPresent()) {
Files.move(
repoBaseDir.resolve(ROOT_FILE_NAME),
repoBaseDir.resolve(
trustedRoot.get().getSignedMeta().getVersion() + "." + ROOT_FILE_NAME));
}
saveRole(root);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public void updateRoot(Path trustedRootPath, URL mirror, TufLocalStore localStor
// 5.3.7) set the trusted root metadata to the new root
trustedRoot = newRoot;
// 5.3.8) persist to repo
localStore.setTrustedRoot(trustedRoot);
localStore.storeTrustedRoot(trustedRoot);
// 5.3.9) see if there are more versions go back 5.3.3
nextVersion++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public interface TufLocalStore {
* If the local store has a root that has been blessed safe either by the client or through update
* and verification, then this method returns it.
*/
Optional<Root> getTrustedRoot();
Optional<Root> loadTrustedRoot() throws IOException;

/**
* Once you have ascertained that your root is trustworthy use this method to persist it to your
Expand All @@ -39,7 +39,7 @@ public interface TufLocalStore {
* @throws IOException since some implementations may persist the root to disk or over the network
* we throw {@code IOException} in case of IO error.
*/
void setTrustedRoot(Root root) throws IOException;
void storeTrustedRoot(Root root) throws IOException;

/**
* This clears out the snapshot and timestamp metadata from the store, as required when snapshot
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,22 @@ class FileSystemTufStoreTest {
@Test
void newFileSystemStore_empty(@TempDir Path repoBase) throws IOException {
TufLocalStore tufLocalStore = FileSystemTufStore.newFileSystemStore(repoBase);
assertFalse(tufLocalStore.getTrustedRoot().isPresent());
assertFalse(tufLocalStore.loadTrustedRoot().isPresent());
}

@Test
void newFileSystemStore_hasRepo(@TempDir Path repoBase) throws IOException {
String repoName = "remote-repo-prod";
TestResources.setupRepoFiles(repoName, repoBase, "root.json");
TufLocalStore tufLocalStore = FileSystemTufStore.newFileSystemStore(repoBase);
assertTrue(tufLocalStore.getTrustedRoot().isPresent());
assertTrue(tufLocalStore.loadTrustedRoot().isPresent());
}

@Test
void setTrustedRoot_noPrevious(@TempDir Path repoBase) throws IOException {
TufLocalStore tufLocalStore = FileSystemTufStore.newFileSystemStore(repoBase);
assertFalse(repoBase.resolve("root.json").toFile().exists());
tufLocalStore.setTrustedRoot(TestResources.loadRoot(TestResources.CLIENT_TRUSTED_ROOT));
tufLocalStore.storeTrustedRoot(TestResources.loadRoot(TestResources.CLIENT_TRUSTED_ROOT));
assertEquals(1, repoBase.toFile().list().length);
assertTrue(repoBase.resolve("root.json").toFile().exists());
}
Expand All @@ -53,9 +53,9 @@ void setTrustedRoot_backupPerformed(@TempDir Path repoBase) throws IOException {
String repoName = "remote-repo-prod";
TestResources.setupRepoFiles(repoName, repoBase, "root.json");
TufLocalStore tufLocalStore = FileSystemTufStore.newFileSystemStore(repoBase);
int version = tufLocalStore.getTrustedRoot().get().getSignedMeta().getVersion();
int version = tufLocalStore.loadTrustedRoot().get().getSignedMeta().getVersion();
assertFalse(repoBase.resolve(version + ".root.json").toFile().exists());
tufLocalStore.setTrustedRoot(TestResources.loadRoot(TestResources.CLIENT_TRUSTED_ROOT));
tufLocalStore.storeTrustedRoot(TestResources.loadRoot(TestResources.CLIENT_TRUSTED_ROOT));
assertTrue(repoBase.resolve(version + ".root.json").toFile().exists());
}

Expand Down

0 comments on commit 9784bd9

Please sign in to comment.