Skip to content

Commit 8e91247

Browse files
authored
Merge pull request #836 from sigstore/store-state-in-updater
Store meta state in memory
2 parents cc82dd2 + 33482eb commit 8e91247

14 files changed

+690
-380
lines changed

sigstore-java/src/main/java/dev/sigstore/tuf/FileSystemTufStore.java

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,21 @@
2828
import java.util.Optional;
2929

3030
/** Uses a local file system directory to store the trusted TUF metadata. */
31-
public class FileSystemTufStore implements MutableTufStore {
31+
public class FileSystemTufStore implements MetaStore, TargetStore {
3232

3333
private static final String ROOT_FILE_NAME = "root.json";
3434
private static final String SNAPSHOT_FILE_NAME = "snapshot.json";
3535
private static final String TIMESTAMP_FILE_NAME = "timestamp.json";
36-
private Path repoBaseDir;
37-
private Path targetsCache;
36+
private final Path repoBaseDir;
37+
private final Path targetsCache;
3838

3939
@VisibleForTesting
4040
FileSystemTufStore(Path repoBaseDir, Path targetsCache) {
4141
this.repoBaseDir = repoBaseDir;
4242
this.targetsCache = targetsCache;
4343
}
4444

45-
public static MutableTufStore newFileSystemStore(Path repoBaseDir) throws IOException {
45+
public static FileSystemTufStore newFileSystemStore(Path repoBaseDir) throws IOException {
4646
if (!Files.isDirectory(repoBaseDir)) {
4747
throw new IllegalArgumentException(repoBaseDir + " must be a file system directory.");
4848
}
@@ -53,7 +53,7 @@ public static MutableTufStore newFileSystemStore(Path repoBaseDir) throws IOExce
5353
return newFileSystemStore(repoBaseDir, defaultTargetsCache);
5454
}
5555

56-
public static MutableTufStore newFileSystemStore(Path repoBaseDir, Path targetsCache) {
56+
public static FileSystemTufStore newFileSystemStore(Path repoBaseDir, Path targetsCache) {
5757
if (!Files.isDirectory(repoBaseDir)) {
5858
throw new IllegalArgumentException(repoBaseDir + " must be a file system directory.");
5959
}
@@ -65,50 +65,26 @@ public static MutableTufStore newFileSystemStore(Path repoBaseDir, Path targetsC
6565

6666
@Override
6767
public String getIdentifier() {
68-
return repoBaseDir.toAbsolutePath().toString();
68+
return "Meta: " + repoBaseDir.toAbsolutePath() + ", Targets:" + targetsCache.toAbsolutePath();
6969
}
7070

7171
@Override
72-
public Optional<Root> loadTrustedRoot() throws IOException {
73-
return loadRole(RootRole.ROOT, Root.class);
74-
}
75-
76-
@Override
77-
public Optional<Timestamp> loadTimestamp() throws IOException {
78-
return loadRole(RootRole.TIMESTAMP, Timestamp.class);
79-
}
80-
81-
@Override
82-
public Optional<Snapshot> loadSnapshot() throws IOException {
83-
return loadRole(RootRole.SNAPSHOT, Snapshot.class);
84-
}
85-
86-
@Override
87-
public Optional<Targets> loadTargets() throws IOException {
88-
return loadRole(RootRole.TARGETS, Targets.class);
89-
}
90-
91-
@Override
92-
public Optional<Targets> loadDelegatedTargets(String roleName) throws IOException {
93-
return loadRole(roleName, Targets.class);
94-
}
95-
96-
@Override
97-
public void storeTargetFile(String targetName, byte[] targetContents) throws IOException {
72+
public void writeTarget(String targetName, byte[] targetContents) throws IOException {
9873
Files.write(targetsCache.resolve(targetName), targetContents);
9974
}
10075

10176
@Override
102-
public byte[] getTargetFile(String targetName) throws IOException {
77+
public byte[] readTarget(String targetName) throws IOException {
10378
return Files.readAllBytes(targetsCache.resolve(targetName));
10479
}
10580

10681
@Override
107-
public void storeMeta(String roleName, SignedTufMeta<?> meta) throws IOException {
82+
public void writeMeta(String roleName, SignedTufMeta<?> meta) throws IOException {
10883
storeRole(roleName, meta);
10984
}
11085

111-
<T extends SignedTufMeta<?>> Optional<T> loadRole(String roleName, Class<T> tClass)
86+
@Override
87+
public <T extends SignedTufMeta<?>> Optional<T> readMeta(String roleName, Class<T> tClass)
11288
throws IOException {
11389
Path roleFile = repoBaseDir.resolve(roleName + ".json");
11490
if (!roleFile.toFile().exists()) {
@@ -125,8 +101,8 @@ <T extends SignedTufMeta<?>> void storeRole(String roleName, T role) throws IOEx
125101
}
126102

127103
@Override
128-
public void storeTrustedRoot(Root root) throws IOException {
129-
Optional<Root> trustedRoot = loadTrustedRoot();
104+
public void writeRoot(Root root) throws IOException {
105+
Optional<Root> trustedRoot = readMeta(RootRole.ROOT, Root.class);
130106
if (trustedRoot.isPresent()) {
131107
try {
132108
Files.move(
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2024 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.tuf;
17+
18+
import dev.sigstore.tuf.model.SignedTufMeta;
19+
import dev.sigstore.tuf.model.TufMeta;
20+
import java.io.IOException;
21+
import java.util.Optional;
22+
23+
/** Interface that defines reading meta from local storage. */
24+
public interface MetaReader {
25+
26+
/**
27+
* Return a named metadata item if there is any.
28+
*
29+
* @param roleName the name of the role to load (root, timestamp, snapshot, targets, or a
30+
* delegated target role)
31+
* @param tClass the class type
32+
* @return an instance of the signed metadata for the role if it was found
33+
* @throws IOException if an error occurs reading from the backing store
34+
*/
35+
<T extends SignedTufMeta<? extends TufMeta>> Optional<T> readMeta(
36+
String roleName, Class<T> tClass) throws IOException;
37+
}

sigstore-java/src/main/java/dev/sigstore/tuf/MutableTufStore.java renamed to sigstore-java/src/main/java/dev/sigstore/tuf/MetaStore.java

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 The Sigstore Authors.
2+
* Copyright 2024 The Sigstore Authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,28 +15,30 @@
1515
*/
1616
package dev.sigstore.tuf;
1717

18-
import dev.sigstore.tuf.model.*;
18+
import dev.sigstore.tuf.model.Root;
19+
import dev.sigstore.tuf.model.SignedTufMeta;
20+
import dev.sigstore.tuf.model.TufMeta;
1921
import java.io.IOException;
2022

21-
/** Defines the set of actions needed to support a local repository of TUF metadata. */
22-
public interface MutableTufStore extends TufStore {
23+
/** Interface that defines a mutable meta store functionality. */
24+
public interface MetaStore extends MetaReader {
25+
2326
/**
24-
* Writes a TUF target to the local target store.
25-
*
26-
* @param targetName the name of the target file to write (e.g. ctfe.pub)
27-
* @param targetContents the content of the target file as bytes
28-
* @throws IOException if an error occurs
27+
* A generic string for identifying the local store in debug messages. A file system based
28+
* implementation might return the path being used for storage, while an in-memory store may just
29+
* return something like 'in-memory'.
2930
*/
30-
void storeTargetFile(String targetName, byte[] targetContents) throws IOException;
31+
String getIdentifier();
3132

3233
/**
33-
* Generic method to store one of the {@link SignedTufMeta} resources in the local tuf store.
34+
* Generic method to store one of the {@link SignedTufMeta} resources in the local tuf store. Do
35+
* not use for Root role, use {@link #writeRoot(Root)} instead.
3436
*
3537
* @param roleName the name of the role
3638
* @param meta the metadata to store
3739
* @throws IOException if writing the resource causes an IO error
3840
*/
39-
void storeMeta(String roleName, SignedTufMeta<?> meta) throws IOException;
41+
void writeMeta(String roleName, SignedTufMeta<? extends TufMeta> meta) throws IOException;
4042

4143
/**
4244
* Once you have ascertained that your root is trustworthy use this method to persist it to your
@@ -49,7 +51,7 @@ public interface MutableTufStore extends TufStore {
4951
* @see <a
5052
* href="https://theupdateframework.github.io/specification/latest/#detailed-client-workflow">5.3.8</a>
5153
*/
52-
void storeTrustedRoot(Root root) throws IOException;
54+
void writeRoot(Root root) throws IOException;
5355

5456
/**
5557
* This clears out the snapshot and timestamp metadata from the store, as required when snapshot
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2024 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.tuf;
17+
18+
import dev.sigstore.tuf.model.Root;
19+
import dev.sigstore.tuf.model.RootRole;
20+
import dev.sigstore.tuf.model.SignedTufMeta;
21+
import dev.sigstore.tuf.model.TufMeta;
22+
import java.io.IOException;
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
import java.util.Objects;
26+
import java.util.Optional;
27+
28+
/** An in memory cache that will pass through to a provided local tuf store. */
29+
public class PassthroughCacheMetaStore implements MetaReader, MetaStore {
30+
private final MetaStore localStore;
31+
private final Map<String, SignedTufMeta<? extends TufMeta>> cache;
32+
33+
private PassthroughCacheMetaStore(MetaStore localStore) {
34+
this.localStore = localStore;
35+
this.cache = new HashMap<>();
36+
}
37+
38+
@Override
39+
public String getIdentifier() {
40+
return "In memory cache backed by: " + localStore.getIdentifier();
41+
}
42+
43+
public static PassthroughCacheMetaStore newPassthroughMetaCache(MetaStore localStore) {
44+
return new PassthroughCacheMetaStore(localStore);
45+
}
46+
47+
@Override
48+
public void writeRoot(Root root) throws IOException {
49+
// call writeRoot instead of generic writeMeta because it may do extra work when storing on disk
50+
localStore.writeRoot(root);
51+
cache.put(RootRole.ROOT, root);
52+
}
53+
54+
@Override
55+
@SuppressWarnings("unchecked")
56+
public <T extends SignedTufMeta<? extends TufMeta>> Optional<T> readMeta(
57+
String roleName, Class<T> tClass) throws IOException {
58+
// check memory cache
59+
if (cache.containsKey(roleName)) {
60+
return Optional.of((T) cache.get(roleName));
61+
}
62+
63+
// check backing storage and write to memory if found
64+
var value = localStore.readMeta(roleName, tClass);
65+
value.ifPresent(v -> cache.put(roleName, v));
66+
67+
return value;
68+
}
69+
70+
@Override
71+
public void writeMeta(String roleName, SignedTufMeta<? extends TufMeta> meta) throws IOException {
72+
if (Objects.equals(roleName, RootRole.ROOT)) {
73+
throw new IllegalArgumentException("Calling writeMeta on root instead of writeRoot");
74+
}
75+
localStore.writeMeta(roleName, meta);
76+
cache.put(roleName, meta);
77+
}
78+
79+
@Override
80+
public void clearMetaDueToKeyRotation() throws IOException {
81+
localStore.clearMetaDueToKeyRotation();
82+
cache.remove(RootRole.TIMESTAMP);
83+
cache.remove(RootRole.SNAPSHOT);
84+
}
85+
}

sigstore-java/src/main/java/dev/sigstore/tuf/SigstoreTufClient.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,18 @@ public SigstoreTufClient build() throws IOException {
130130
remoteMirror.toString().endsWith("/")
131131
? remoteMirror
132132
: new URL(remoteMirror.toExternalForm() + "/");
133-
var targetsLocation = new URL(normalizedRemoteMirror.toExternalForm() + "targets");
133+
var remoteTargetsLocation = new URL(normalizedRemoteMirror.toExternalForm() + "targets");
134+
var filesystemTufStore = FileSystemTufStore.newFileSystemStore(tufCacheLocation);
134135
var tufUpdater =
135136
Updater.builder()
136137
.setTrustedRootPath(trustedRoot)
137-
.setLocalStore(FileSystemTufStore.newFileSystemStore(tufCacheLocation))
138+
.setTrustedMetaStore(
139+
TrustedMetaStore.newTrustedMetaStore(
140+
PassthroughCacheMetaStore.newPassthroughMetaCache(filesystemTufStore)))
141+
.setTargetStore(filesystemTufStore)
138142
.setMetaFetcher(
139143
MetaFetcher.newFetcher(HttpFetcher.newFetcher(normalizedRemoteMirror)))
140-
.setTargetFetcher(HttpFetcher.newFetcher(targetsLocation))
144+
.setTargetFetcher(HttpFetcher.newFetcher(remoteTargetsLocation))
141145
.build();
142146
return new SigstoreTufClient(tufUpdater, cacheValidity);
143147
}
@@ -166,7 +170,7 @@ public void forceUpdate()
166170
JsonFormat.parser()
167171
.merge(
168172
new String(
169-
updater.getLocalStore().getTargetFile(TRUST_ROOT_FILENAME), StandardCharsets.UTF_8),
173+
updater.getTargetStore().readTarget(TRUST_ROOT_FILENAME), StandardCharsets.UTF_8),
170174
trustedRootBuilder);
171175
sigstoreTrustedRoot = SigstoreTrustedRoot.from(trustedRootBuilder.build());
172176
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2024 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.tuf;
17+
18+
import java.io.IOException;
19+
20+
/** Interface that defines reading targets from local storage. */
21+
public interface TargetReader {
22+
23+
/**
24+
* Reads a TUF target file from the local TUF store
25+
*
26+
* @param targetName the name of the target file to read (e.g. ctfe.pub)
27+
* @return the content of the file as bytes
28+
* @throws IOException if an error occurs
29+
*/
30+
byte[] readTarget(String targetName) throws IOException;
31+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2024 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.tuf;
17+
18+
import java.io.IOException;
19+
20+
/** Interface that defines a mutable target store functionality. */
21+
public interface TargetStore extends TargetReader {
22+
23+
/**
24+
* A generic string for identifying the local store in debug messages. A file system based
25+
* implementation might return the path being used for storage, while an in-memory store may just
26+
* return something like 'in-memory'.
27+
*/
28+
String getIdentifier();
29+
30+
/**
31+
* Writes a TUF target to the local target store.
32+
*
33+
* @param targetName the name of the target file to write (e.g. ctfe.pub)
34+
* @param targetContents the content of the target file as bytes
35+
* @throws IOException if an error occurs
36+
*/
37+
void writeTarget(String targetName, byte[] targetContents) throws IOException;
38+
}

0 commit comments

Comments
 (0)