-
Notifications
You must be signed in to change notification settings - Fork 57
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
Add different run modes for the TUF server, allow saving TUF keys as a secret #1214
Conversation
…a secret This commit implements features necessary to run/operate the TUF server in production much better: * The TUF server can now be run in 4 different modes: * `init` - only init the TUF repository and exit * `init-no-overwrite` - same as `init`, but won't overwrite the TUF repository if it already exists * `serve` - only serve an existing TUF repository (no `init`) * `init-and-serve` - `init` and then `serve` * The TUF repository can now be initialized/served from a given path, as opposed to always living in a `/tmp` directory. * The TUF keys can optionally be exported as an individual k8s secret. Signed-off-by: Slavek Kabrda <[email protected]>
Signed-off-by: Slavek Kabrda <[email protected]>
Signed-off-by: Slavek Kabrda <[email protected]>
637879b
to
f5e033c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes sense: it makes it clearer that this is really just file serving code with a single function to (now optionally) initialize that file server with some generated TUF content.
I don't comment on the secrets management: I don't know this technology at all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@haydentherapper asked me to look at this. The k8s part makes sense, but I had a few questions about the general patterns since I'm not as familiar with the codebase.
Splitting init
and serve
makes a lot of sense to me. I am wondering about the "saving the keys" vs simply pulling the keys out of the existing saved secret.
const ( | ||
modeInit = "init" | ||
modeInitAndServe = "init-and-serve" | ||
modeServe = "serve" | ||
modeInitNoOverwrite = "init-no-overwrite" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we were using Cobra, I'd suggest using subcommands, but I don't want to pull in that dependency randomly.
cmd/tuf/server/main.go
Outdated
// Name of the "secret" where we create two entries, one for: | ||
// root = Which holds 1.root.json | ||
// repository - Compressed repo, which has been tar/gzipped. | ||
secretName = flag.String("rootsecret", "tuf-root", "Name of the secret to create for the initial root file") | ||
noK8s = flag.Bool("no-k8s", false, "Run in a non-k8s environment") | ||
// Name of the "secret" where we create one entry "keys" which is a compressed directory of private keys generated by TUF |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Name of the "secret" where we create one entry "keys" which is a compressed directory of private keys generated by TUF | |
// Name of the "secret" where we create one entry "keys" which is a tar/gzipped directory of private keys generated by TUF |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(We should be clear about the format)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this comment is actually no longer true. It was true in the first version of my code, but now it is not tar/gzipped, but it's literally a secret with key/value pairs for the 4 individual keys. I will update the comment.
case modeInitAndServe: | ||
init = true | ||
serve = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming there's no use case for InitNoOverwriteAndServe
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally don't have such a usecase right now and I always try to keep new stuff at minimum. If we ever see a need for it in the future, it will be easy to add it in a fully backwards compatible way.
|
||
ctx := signals.NewContext() | ||
|
||
func initTUFRepo(ctx context.Context, certsDir, targetDir, repoSecretName, keysSecretName string) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracting this is a nice win anyway, though it's unfortunate that we have 4 string-type arguments next to each other. Oh well.
cmd/tuf/server/main.go
Outdated
trimDir := strings.TrimSuffix(*dir, "/") | ||
trimDir := strings.TrimSuffix(certsDir, "/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not your fault, but it feels like we should move trimDir
above line 88 (os.ReadDir
) and use it consistently from that point onwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, I can do that.
cmd/tuf/server/main.go
Outdated
@@ -95,7 +103,7 @@ func main() { | |||
fileName := fmt.Sprintf("%s/%s", trimDir, file.Name()) | |||
fileBytes, err := os.ReadFile(fileName) | |||
if err != nil { | |||
logging.FromContext(ctx).Fatalf("failed to read file %s/%s: %v", fileName, err) | |||
return fmt.Errorf("failed to read file %s/%s: %v", trimDir, fileName, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fileName
already contains trimDir
-- this will look like:
failed to read file /tmp/tmp/foo: bad link
or what-not.
cmd/tuf/server/main.go
Outdated
@@ -104,7 +112,7 @@ func main() { | |||
|
|||
certFiles, err := certs.SplitCertChain(fileBytes, "tsa") | |||
if err != nil { | |||
logging.FromContext(ctx).Fatalf("failed to parse %s/%s: %v", fileName, err) | |||
return fmt.Errorf("failed to parse %s/%s: %v", trimDir, fileName, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto.
|
||
// If we should also store created keys in a secret, read all their files and save them in the secret | ||
if keysSecretName != "" { | ||
keyFiles, err := os.ReadDir(filepath.Join(dir, "keys")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't we pack the keys
directory into data["repository"]
already, along with the rest of dir
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We didn't. If you look at how this is done:
if err := repo.CompressFS(os.DirFS(dir), &compressed, map[string]bool{"keys": true, "staged": true}); err != nil
and how the CompressFS
function works, it will actually intentionally skip the keys
and staged
directories because of how it is called.
|
||
// Copy repository to the targetDir - until Go 1.23 which has os.CopyFS, we use | ||
// a quick hack where we uncompress the compressed repository to the targetDir | ||
repo.Uncompress(bytes.NewReader(data["repository"]), targetDir) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Right now, it seems like we're creating in a temp directory and then moving to the final destination. What about having the repo.CreateRepo
take targetDir
as an argument, and not needing to do this unpack?
At that point, the code could also look like:
if noK8s {
// We're done
return nil
}
I suppose one advantage of the copy approach is that you won't end up with a half-initialized directory if you crash partway through....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There were two reasons why I didn't add the extra argument, even though we considered it:
- Exactly as you said, I think it's good to not end up with a potentially half-initialized directory.
- Adding the new argument would change the public API of
repo.CreateRepo
. While I think that the modules from this repo might not be depended on very much, I wanted to prevent API breakage (but actually talking about this right now, I'm curious if that's ok - for future contributions).
Signed-off-by: Slavek Kabrda <[email protected]>
e5da94f
to
88ac0e1
Compare
@evankanderson thanks a lot for your review! I think I addressed all the points. As noted in one of the inline comments, the currently saved secret intentionally explicitly excludes the keys directory from being saved. I'm not sure why it was coded this way, but I think it makes sense that these two are independent and the keys secret can be downloaded and deleted altogether. (I don't think this is a huge benefit, but it felt to me that it might be nice, YMMV of course). |
…a secret (sigstore#1214) * Add different run modes for the TUF server, allow saving TUF keys as a secret This commit implements features necessary to run/operate the TUF server in production much better: * The TUF server can now be run in 4 different modes: * `init` - only init the TUF repository and exit * `init-no-overwrite` - same as `init`, but won't overwrite the TUF repository if it already exists * `serve` - only serve an existing TUF repository (no `init`) * `init-and-serve` - `init` and then `serve` * The TUF repository can now be initialized/served from a given path, as opposed to always living in a `/tmp` directory. * The TUF keys can optionally be exported as an individual k8s secret. Signed-off-by: Slavek Kabrda <[email protected]>
Summary
Addresses part of #1182.
This commit implements features necessary to run/operate the TUF server in production much better:
init
- only init the TUF repository and exitinit-no-overwrite
- same asinit
, but won't overwrite the TUF repository if it already existsserve
- only serve an existing TUF repository (noinit
)init-and-serve
-init
and thenserve
(this is default to preserve backwards compatibility)/tmp
directory.Some notes:
init
orinit-no-overwrite
to create the TUF repository at a mounted volume.serve
to serve the TUF repository from the mounted volume.Release Note
init
- only init the TUF repository and exitinit-no-overwrite
- same asinit
, but won't overwrite the TUF repository if it already existsserve
- only serve an existing TUF repository (noinit
)init-and-serve
-init
and thenserve
(this is default to preserve backwards compatibility)/tmp
directory.Documentation
It feels like this change would deserve some docs, but I don't know if there even are docs for the TUF server. I can certainly write something up if you can point me to the right place.