Files
Etienne Perot 0db57431b2 Enforce release matching in sidecar binaries.
This ensures that sidecars must be installed from the same release.

See issue #13718.
2026-07-22 16:28:21 -07:00

176 lines
6.3 KiB
Go

// Copyright 2025 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Binary checkpointgofer implements the checkpoint gofer, which provides
// remote checkpoint file access.
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/google/subcommands"
"golang.org/x/oauth2"
"gvisor.dev/gvisor/pkg/sentry/state/stateipc"
"gvisor.dev/gvisor/pkg/unet"
"gvisor.dev/gvisor/pkg/urpc"
"gvisor.dev/gvisor/runsc/checkpointgofer/gcs"
"gvisor.dev/gvisor/runsc/cli"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/flag"
"gvisor.dev/gvisor/runsc/gvisorbinaries"
)
// GCSOptions holds options that configure a checkpoint gofer to access GCS.
type GCSOptions struct {
// If Token is non-empty, the checkpoint gofer will use it for
// authentication.
//
// Otherwise, the checkpoint gofer will use application default
// credentials.
Token *oauth2.Token `json:"token,omitzero"`
// Bucket is the GCS bucket containing checkpoint files.
Bucket string `json:"bucket"`
// ObjectPrefix is prepended to checkpoint file names to form GCS object
// names. Note that ObjectPrefix is prepended as a string, not a path, so
// if ObjectPrefix does not have a trailing "/", no "/" will be inserted
// between ObjectPrefix and the filename.
ObjectPrefix string `json:"object_prefix"`
// ParallelCompositeUpload controls whether parallel composite upload is
// used for writing the pages file. Valid values are:
// - "safe": enable parallel composite upload if safe
// - "disable": disable parallel composite upload unconditionally
// - "force": enable parallel composite upload unconditionally
ParallelCompositeUpload string `json:"parallel_composite_upload,omitzero"`
}
// checkpointGoferCmd implements subcommands.Command for the checkpoint gofer.
type checkpointGoferCmd struct {
util.InternalSubCommand
allowCheckpointReads bool
allowCheckpointWrites bool
allowFSCheckpointReads bool
allowFSCheckpointWrites bool
sockFD int
gcsOptsFD int
}
// Name implements subcommands.Command.Name.
func (*checkpointGoferCmd) Name() string {
return "checkpointgofer"
}
// Synopsis implements subcommands.Command.Synopsis.
func (*checkpointGoferCmd) Synopsis() string {
return "runs process for remote checkpoint file access"
}
// Usage implements subcommands.Command.Usage.
func (*checkpointGoferCmd) Usage() string {
return "[-allow-checkpoint-reads|-allow-checkpoint-writes|-allow-fscheckpoint-reads|-allow-fscheckpoint-writes] -sock-fd=<socket fd> -gcs-opts-fd=<options fd>\n"
}
// SetFlags implements subcommands.Command.SetFlags.
func (cmd *checkpointGoferCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&cmd.allowCheckpointReads, "allow-checkpoint-reads", false, "enable reading checkpoint files")
f.BoolVar(&cmd.allowCheckpointWrites, "allow-checkpoint-writes", false, "enable writing checkpoint files")
f.BoolVar(&cmd.allowFSCheckpointReads, "allow-fscheckpoint-reads", false, "enable reading filesystem checkpoint files")
f.BoolVar(&cmd.allowFSCheckpointWrites, "allow-fscheckpoint-writes", false, "enable writing filesystem checkpoint files")
f.IntVar(&cmd.sockFD, "sock-fd", -1, "FD for a Unix domain socket that is connected to the sentry")
f.IntVar(&cmd.gcsOptsFD, "gcs-opts-fd", -1, "FD for a file containing GCSOptions in JSON")
}
// Execute implements subcommands.Command.Execute.
func (cmd *checkpointGoferCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
if (!cmd.allowCheckpointReads && !cmd.allowCheckpointWrites && !cmd.allowFSCheckpointReads && !cmd.allowFSCheckpointWrites) || cmd.sockFD < 0 || cmd.gcsOptsFD < 0 {
f.Usage()
return subcommands.ExitUsageError
}
sock, err := unet.NewSocket(cmd.sockFD)
if err != nil {
util.Fatalf("Failed to construct unet.Socket: %v", err)
}
gcsOptsFile := os.NewFile(uintptr(cmd.gcsOptsFD), "checkpointgofer.GCSOptions.json")
var gcsOpts GCSOptions
if err := json.NewDecoder(gcsOptsFile).Decode(&gcsOpts); err != nil {
util.Fatalf("Failed to decode options: %v", err)
}
gcsOptsFile.Close()
if err := cmd.runGCS(ctx, sock, &gcsOpts); err != nil {
util.Fatalf("%v", err)
}
util.Infof("Server stopped, exiting")
return subcommands.ExitSuccess
}
// runGCS runs the checkpoint gofer for GCS access on the given socket. It does
// not return until the client disconnects or a fatal error occurs.
func (cmd *checkpointGoferCmd) runGCS(ctx context.Context, sock *unet.Socket, opts *GCSOptions) error {
if len(opts.Bucket) == 0 {
return fmt.Errorf("bucket must be specified")
}
gcsfsOpts := gcs.FileServerOptions{
AllowCheckpointReads: cmd.allowCheckpointReads,
AllowCheckpointWrites: cmd.allowCheckpointWrites,
AllowFSCheckpointReads: cmd.allowFSCheckpointReads,
AllowFSCheckpointWrites: cmd.allowFSCheckpointWrites,
Bucket: opts.Bucket,
ObjectPrefix: opts.ObjectPrefix,
}
if opts.Token != nil {
gcsfsOpts.TokenSource = oauth2.StaticTokenSource(opts.Token)
}
switch opts.ParallelCompositeUpload {
case "", "safe":
gcsfsOpts.ParallelCompositeUpload = gcs.ParallelCompositeUploadSafe
case "disable":
gcsfsOpts.ParallelCompositeUpload = gcs.ParallelCompositeUploadDisable
case "force":
gcsfsOpts.ParallelCompositeUpload = gcs.ParallelCompositeUploadForce
default:
return fmt.Errorf("unknown parallel_composite_upload value %q", opts.ParallelCompositeUpload)
}
gcsfs, err := gcs.NewFileServer(ctx, &gcsfsOpts)
if err != nil {
return fmt.Errorf("failed to construct GCS file server: %w", err)
}
afs, err := stateipc.NewAsyncFileServer(gcsfs)
if err != nil {
return fmt.Errorf("failed to construct stateipc server: %w", err)
}
server := urpc.NewServer()
server.Register(afs)
server.Handle(sock)
return nil
}
// main is the binary's entry point.
func main() {
cli.Run(&gvisorbinaries.CheckpointGofer, map[util.SubCommand]string{
new(checkpointGoferCmd): "",
}, nil)
}