mirror of
https://github.com/containers/skopeo.git
synced 2026-02-04 08:18:52 +00:00
Compare commits
3 Commits
main
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48a05d71f0 | ||
|
|
dec587b480 | ||
|
|
8bd9c541f0 |
@@ -157,7 +157,7 @@ ostree-rs-ext_task:
|
||||
dockerfile: contrib/cirrus/ostree_ext.dockerfile
|
||||
docker_arguments: # required build-args
|
||||
BASE_FQIN: quay.io/coreos-assembler/fcos-buildroot:testing-devel
|
||||
CIRRUS_IMAGE_VERSION: 4
|
||||
CIRRUS_IMAGE_VERSION: 3
|
||||
env:
|
||||
EXT_REPO_NAME: ostree-rs-ext
|
||||
EXT_REPO_HOME: $CIRRUS_WORKING_DIR/../$EXT_REPO_NAME
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
version: "2"
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
|
||||
linters:
|
||||
settings:
|
||||
staticcheck:
|
||||
|
||||
@@ -90,8 +90,7 @@ jobs:
|
||||
|
||||
# Tests on ELN for main branch
|
||||
- job: tests
|
||||
# FIXME: https://github.com/containers/skopeo/issues/2748
|
||||
trigger: ignore
|
||||
trigger: pull_request
|
||||
packages: [skopeo-eln]
|
||||
notifications: *test_failure_notification
|
||||
targets: *eln_copr_targets
|
||||
|
||||
7
Makefile
7
Makefile
@@ -29,7 +29,7 @@ SEQUOIA_SONAME_DIR =
|
||||
# N/B: This value is managed by Renovate, manual changes are
|
||||
# possible, as long as they don't disturb the formatting
|
||||
# (i.e. DO NOT ADD A 'v' prefix!)
|
||||
GOLANGCI_LINT_VERSION := 2.8.0
|
||||
GOLANGCI_LINT_VERSION := 2.6.1
|
||||
|
||||
ifeq ($(GOBIN),)
|
||||
GOBIN := $(GOPATH)/bin
|
||||
@@ -242,13 +242,10 @@ validate:
|
||||
# This target is only intended for development, e.g. executing it from an IDE. Use (make test) for CI or pre-release testing.
|
||||
test-all-local: validate-local validate-docs test-unit-local
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: tools
|
||||
$(GOBIN)/golangci-lint fmt
|
||||
|
||||
.PHONY: validate-local
|
||||
validate-local: tools
|
||||
hack/validate-git-marks.sh
|
||||
hack/validate-gofmt.sh
|
||||
$(GOBIN)/golangci-lint run --build-tags "${BUILDTAGS}"
|
||||
# An extra run with --tests=false allows detecting code unused outside of tests;
|
||||
# ideally the linter should be able to find this automatically.
|
||||
|
||||
@@ -45,8 +45,7 @@ func copyCmd(global *globalOptions) *cobra.Command {
|
||||
destFlags, destOpts := imageDestFlags(global, sharedOpts, deprecatedTLSVerifyOpt, "dest-", "dcreds")
|
||||
retryFlags, retryOpts := retryFlags()
|
||||
copyFlags, copyOpts := sharedCopyFlags()
|
||||
opts := copyOptions{
|
||||
global: global,
|
||||
opts := copyOptions{global: global,
|
||||
deprecatedTLSVerify: deprecatedTLSVerifyOpt,
|
||||
srcImage: srcOpts,
|
||||
destImage: destOpts,
|
||||
@@ -252,7 +251,7 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) (retErr error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.WriteFile(opts.digestFile, []byte(manifestDigest.String()), 0o644); err != nil {
|
||||
if err = os.WriteFile(opts.digestFile, []byte(manifestDigest.String()), 0644); err != nil {
|
||||
return fmt.Errorf("Failed to write digest to file %q: %w", opts.digestFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,10 +79,10 @@ func (opts *generateSigstoreKeyOptions) run(args []string, stdout io.Writer) err
|
||||
return fmt.Errorf("Error generating key pair: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(privateKeyPath, keys.PrivateKey, 0o600); err != nil {
|
||||
if err := os.WriteFile(privateKeyPath, keys.PrivateKey, 0600); err != nil {
|
||||
return fmt.Errorf("Error writing private key to %q: %w", privateKeyPath, err)
|
||||
}
|
||||
if err := os.WriteFile(pubKeyPath, keys.PublicKey, 0o644); err != nil {
|
||||
if err := os.WriteFile(pubKeyPath, keys.PublicKey, 0644); err != nil {
|
||||
return fmt.Errorf("Error writing private key to %q: %w", pubKeyPath, err)
|
||||
}
|
||||
fmt.Fprintf(stdout, "Key written to %q and %q\n", privateKeyPath, pubKeyPath)
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestGenerateSigstoreKey(t *testing.T) {
|
||||
for _, suffix := range outputSuffixes {
|
||||
dir := t.TempDir()
|
||||
prefix := filepath.Join(dir, "prefix")
|
||||
err := os.WriteFile(prefix+suffix, []byte{}, 0o600)
|
||||
err := os.WriteFile(prefix+suffix, []byte{}, 0600)
|
||||
require.NoError(t, err)
|
||||
out, err := runSkopeo("generate-sigstore-key",
|
||||
"--output-prefix", prefix, "--passphrase-file", "/dev/null",
|
||||
@@ -37,7 +37,7 @@ func TestGenerateSigstoreKey(t *testing.T) {
|
||||
for _, suffix := range outputSuffixes {
|
||||
dir := t.TempDir()
|
||||
nonDirectory := filepath.Join(dir, "nondirectory")
|
||||
err := os.WriteFile(nonDirectory, []byte{}, 0o600)
|
||||
err := os.WriteFile(nonDirectory, []byte{}, 0600)
|
||||
require.NoError(t, err)
|
||||
prefix := filepath.Join(dir, "prefix")
|
||||
err = os.Symlink(filepath.Join(nonDirectory, "unaccessible"), prefix+suffix)
|
||||
@@ -66,7 +66,7 @@ func TestGenerateSigstoreKey(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
prefix := filepath.Join(dir, "prefix")
|
||||
passphraseFile := filepath.Join(dir, "passphrase")
|
||||
err = os.WriteFile(passphraseFile, []byte("some passphrase"), 0o600)
|
||||
err = os.WriteFile(passphraseFile, []byte("some passphrase"), 0600)
|
||||
require.NoError(t, err)
|
||||
out, err = runSkopeo("generate-sigstore-key",
|
||||
"--output-prefix", prefix, "--passphrase-file", passphraseFile,
|
||||
@@ -75,4 +75,5 @@ func TestGenerateSigstoreKey(t *testing.T) {
|
||||
for _, suffix := range outputSuffixes {
|
||||
assert.Contains(t, out, prefix+suffix)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/containers/skopeo/cmd/skopeo/inspect"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
"github.com/opencontainers/go-digest"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -23,14 +22,13 @@ import (
|
||||
)
|
||||
|
||||
type inspectOptions struct {
|
||||
global *globalOptions
|
||||
image *imageOptions
|
||||
retryOpts *retry.Options
|
||||
format string
|
||||
raw bool // Output the raw manifest instead of parsing information about the image
|
||||
config bool // Output the raw config blob instead of parsing information about the image
|
||||
doNotListTags bool // Do not list all tags available in the same repository
|
||||
manifestDigest digest.Algorithm // Algorithm to use for computing manifest digest
|
||||
global *globalOptions
|
||||
image *imageOptions
|
||||
retryOpts *retry.Options
|
||||
format string
|
||||
raw bool // Output the raw manifest instead of parsing information about the image
|
||||
config bool // Output the raw config blob instead of parsing information about the image
|
||||
doNotListTags bool // Do not list all tags available in the same repository
|
||||
}
|
||||
|
||||
func inspectCmd(global *globalOptions) *cobra.Command {
|
||||
@@ -66,7 +64,6 @@ skopeo inspect --format "Name: {{.Name}} Digest: {{.Digest}}" docker://registry.
|
||||
flags.BoolVar(&opts.config, "config", false, "output configuration")
|
||||
flags.StringVarP(&opts.format, "format", "f", "", "Format the output to a Go template")
|
||||
flags.BoolVarP(&opts.doNotListTags, "no-tags", "n", false, "Do not list the available tags from the repository in the output")
|
||||
flags.Var(newAlgorithmValue(&opts.manifestDigest), "manifest-digest", "Algorithm to use for computing manifest digest (sha256, sha512); defaults to algorithm used in config digest")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -179,7 +176,7 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
|
||||
LayersData: imgInspect.LayersData,
|
||||
Env: imgInspect.Env,
|
||||
}
|
||||
outputData.Digest, err = manifestDigestFromManifest(rawManifest, img, opts.manifestDigest)
|
||||
outputData.Digest, err = manifest.Digest(rawManifest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error computing manifest digest: %w", err)
|
||||
}
|
||||
@@ -238,48 +235,3 @@ func (opts *inspectOptions) writeOutput(stdout io.Writer, data any) error {
|
||||
defer rpt.Flush()
|
||||
return rpt.Execute([]any{data})
|
||||
}
|
||||
|
||||
func manifestDigestFromManifest(manifestBlob []byte, img types.Image, userAlgorithm digest.Algorithm) (digest.Digest, error) {
|
||||
if userAlgorithm != "" {
|
||||
if !userAlgorithm.Available() {
|
||||
return "", fmt.Errorf("digest algorithm %q is not available", userAlgorithm)
|
||||
}
|
||||
return manifest.DigestWithAlgorithm(manifestBlob, userAlgorithm)
|
||||
}
|
||||
|
||||
configInfo := img.ConfigInfo()
|
||||
if configInfo.Digest != "" {
|
||||
alg := configInfo.Digest.Algorithm()
|
||||
if !alg.Available() {
|
||||
return "", fmt.Errorf("config digest algorithm %q is not available", alg)
|
||||
}
|
||||
return manifest.DigestWithAlgorithm(manifestBlob, alg)
|
||||
}
|
||||
|
||||
return manifest.Digest(manifestBlob)
|
||||
}
|
||||
|
||||
type algorithmValue digest.Algorithm
|
||||
|
||||
func newAlgorithmValue(alg *digest.Algorithm) *algorithmValue {
|
||||
return (*algorithmValue)(alg)
|
||||
}
|
||||
|
||||
func (a *algorithmValue) Set(value string) error {
|
||||
algorithm := digest.Algorithm(value)
|
||||
|
||||
*a = algorithmValue(algorithm)
|
||||
if algorithm == "" {
|
||||
*a = algorithmValue(digest.Canonical)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *algorithmValue) String() string {
|
||||
return digest.Algorithm(*a).String()
|
||||
}
|
||||
|
||||
func (a *algorithmValue) Type() string {
|
||||
return "algorithm"
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
// Tests the kinds of inputs allowed and expected to the command
|
||||
func TestDockerRepositoryReferenceParser(t *testing.T) {
|
||||
for _, test := range [][]string{
|
||||
{"docker://myhost.com:1000/nginx"}, // no tag
|
||||
{"docker://myhost.com/nginx"}, // no port or tag
|
||||
{"docker://myhost.com:1000/nginx"}, //no tag
|
||||
{"docker://myhost.com/nginx"}, //no port or tag
|
||||
{"docker://somehost.com"}, // Valid default expansion
|
||||
{"docker://nginx"}, // Valid default expansion
|
||||
} {
|
||||
@@ -31,8 +31,8 @@ func TestDockerRepositoryReferenceParser(t *testing.T) {
|
||||
{"docker-daemon:myhost.com/someimage"},
|
||||
{"docker://myhost.com:1000/nginx:foobar:foobar"}, // Invalid repository ref
|
||||
{"docker://somehost.com:5000/"}, // no repo
|
||||
{"docker://myhost.com:1000/nginx:latest"}, // tag not allowed
|
||||
{"docker://myhost.com:1000/nginx@sha256:abcdef1234567890"}, // digest not allowed
|
||||
{"docker://myhost.com:1000/nginx:latest"}, //tag not allowed
|
||||
{"docker://myhost.com:1000/nginx@sha256:abcdef1234567890"}, //digest not allowed
|
||||
} {
|
||||
_, err := parseDockerRepositoryReference(test[0])
|
||||
assert.Error(t, err, test[0])
|
||||
@@ -41,8 +41,8 @@ func TestDockerRepositoryReferenceParser(t *testing.T) {
|
||||
|
||||
func TestDockerRepositoryReferenceParserDrift(t *testing.T) {
|
||||
for _, test := range [][]string{
|
||||
{"docker://myhost.com:1000/nginx", "myhost.com:1000/nginx"}, // no tag
|
||||
{"docker://myhost.com/nginx", "myhost.com/nginx"}, // no port or tag
|
||||
{"docker://myhost.com:1000/nginx", "myhost.com:1000/nginx"}, //no tag
|
||||
{"docker://myhost.com/nginx", "myhost.com/nginx"}, //no port or tag
|
||||
{"docker://somehost.com", "docker.io/library/somehost.com"}, // Valid default expansion
|
||||
{"docker://nginx", "docker.io/library/nginx"}, // Valid default expansion
|
||||
} {
|
||||
|
||||
@@ -31,7 +31,6 @@ type globalOptions struct {
|
||||
registriesConfPath string // Path to the "registries.conf" file
|
||||
tmpDir string // Path to use for big temporary files
|
||||
userAgentPrefix string // Prefix to add to the user agent string
|
||||
requireSigned bool // Require any pulled image to be signed
|
||||
}
|
||||
|
||||
// requireSubcommand returns an error if no sub command is provided
|
||||
@@ -82,7 +81,6 @@ func createApp() (*cobra.Command, *globalOptions) {
|
||||
rootCommand.PersistentFlags().BoolVar(&opts.debug, "debug", false, "enable debug output")
|
||||
rootCommand.PersistentFlags().StringVar(&opts.policyPath, "policy", "", "Path to a trust policy file")
|
||||
rootCommand.PersistentFlags().BoolVar(&opts.insecurePolicy, "insecure-policy", false, "run the tool without any policy check")
|
||||
rootCommand.PersistentFlags().BoolVar(&opts.requireSigned, "require-signed", false, "require any pulled image to be signed")
|
||||
rootCommand.PersistentFlags().StringVar(&opts.registriesDirPath, "registries.d", "", "use registry configuration files in `DIR` (e.g. for container signature storage)")
|
||||
rootCommand.PersistentFlags().StringVar(&opts.overrideArch, "override-arch", "", "use `ARCH` instead of the architecture of the machine for choosing images")
|
||||
rootCommand.PersistentFlags().StringVar(&opts.overrideOS, "override-os", "", "use `OS` instead of the running OS for choosing images")
|
||||
@@ -137,9 +135,6 @@ func (opts *globalOptions) before(cmd *cobra.Command, args []string) error {
|
||||
if opts.tlsVerify.Present() {
|
||||
logrus.Warn("'--tls-verify' is deprecated, please set this on the specific subcommand")
|
||||
}
|
||||
if opts.insecurePolicy && opts.requireSigned {
|
||||
return fmt.Errorf("--insecure-policy and --require-signed are mutually exclusive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -171,14 +166,7 @@ func (opts *globalOptions) getPolicyContext() (*signature.PolicyContext, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc, err := signature.NewPolicyContext(policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.requireSigned {
|
||||
pc.RequireSignatureVerification(true)
|
||||
}
|
||||
return pc, nil
|
||||
return signature.NewPolicyContext(policy)
|
||||
}
|
||||
|
||||
// commandTimeoutContext returns a context.Context and a cancellation callback based on opts.
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
"go.podman.io/image/v5/manifest"
|
||||
)
|
||||
|
||||
type manifestDigestOptions struct{}
|
||||
type manifestDigestOptions struct {
|
||||
}
|
||||
|
||||
func manifestDigestCmd() *cobra.Command {
|
||||
var opts manifestDigestOptions
|
||||
|
||||
@@ -61,7 +61,7 @@ func (opts *standaloneSignOptions) run(args []string, stdout io.Writer) error {
|
||||
return fmt.Errorf("Error creating signature: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(opts.output, signature, 0o644); err != nil {
|
||||
if err := os.WriteFile(opts.output, signature, 0644); err != nil {
|
||||
return fmt.Errorf("Error writing signature to %s: %w", opts.output, err)
|
||||
}
|
||||
return nil
|
||||
@@ -118,6 +118,7 @@ func (opts *standaloneVerifyOptions) run(args []string, stdout io.Writer) error
|
||||
mech, publicKeyfingerprints, err = signature.NewEphemeralGPGSigningMechanism(publicKeys)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error initializing GPG: %w", err)
|
||||
|
||||
}
|
||||
} else {
|
||||
mech, err = signature.NewGPGSigningMechanism()
|
||||
@@ -146,7 +147,8 @@ func (opts *standaloneVerifyOptions) run(args []string, stdout io.Writer) error
|
||||
// (including things like “✅ Verified by $authority”)
|
||||
//
|
||||
// The subcommand is undocumented, and it may be renamed or entirely disappear in the future.
|
||||
type untrustedSignatureDumpOptions struct{}
|
||||
type untrustedSignatureDumpOptions struct {
|
||||
}
|
||||
|
||||
func untrustedSignatureDumpCmd() *cobra.Command {
|
||||
opts := untrustedSignatureDumpOptions{}
|
||||
|
||||
@@ -182,7 +182,7 @@ func destinationReference(destination string, transport string) (types.ImageRefe
|
||||
return nil, fmt.Errorf("Destination directory could not be used: %w", err)
|
||||
}
|
||||
// the directory holding the image must be created here
|
||||
if err = os.MkdirAll(destination, 0o755); err != nil {
|
||||
if err = os.MkdirAll(destination, 0755); err != nil {
|
||||
return nil, fmt.Errorf("Error creating directory for image %s: %w", destination, err)
|
||||
}
|
||||
imageTransport = directory.Transport
|
||||
@@ -270,6 +270,7 @@ func imagesToCopyFromDir(dirPath string) ([]types.ImageReference, error) {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return sourceReferences,
|
||||
fmt.Errorf("Error walking the path %q: %w", dirPath, err)
|
||||
@@ -366,8 +367,7 @@ func imagesToCopyFromRegistry(registryName string, cfg registrySyncConfig, sourc
|
||||
}
|
||||
repoDescList = append(repoDescList, repoDescriptor{
|
||||
ImageRefs: sourceReferences,
|
||||
Context: serverCtx,
|
||||
})
|
||||
Context: serverCtx})
|
||||
}
|
||||
|
||||
// include repository descriptors for cfg.ImagesByTagRegex
|
||||
@@ -667,7 +667,7 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) (retErr error) {
|
||||
|
||||
var digestFile *os.File
|
||||
if opts.digestFile != "" && !opts.dryRun {
|
||||
digestFile, err = os.OpenFile(opts.digestFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
digestFile, err = os.OpenFile(opts.digestFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating digest file: %w", err)
|
||||
}
|
||||
|
||||
@@ -49,8 +49,7 @@ func fakeGlobalOptions(t *testing.T, flags []string) (*globalOptions, *cobra.Com
|
||||
|
||||
// fakeImageOptions creates imageOptions and sets it according to globalFlags/cmdFlags.
|
||||
func fakeImageOptions(t *testing.T, flagPrefix string, useDeprecatedTLSVerify bool,
|
||||
globalFlags []string, cmdFlags []string,
|
||||
) *imageOptions {
|
||||
globalFlags []string, cmdFlags []string) *imageOptions {
|
||||
globalOpts, cmd := fakeGlobalOptions(t, globalFlags)
|
||||
sharedFlags, sharedOpts := sharedImageFlags()
|
||||
var deprecatedTLSVerifyFlag pflag.FlagSet
|
||||
@@ -125,8 +124,7 @@ func TestImageOptionsNewSystemContext(t *testing.T) {
|
||||
|
||||
// fakeImageDestOptions creates imageDestOptions and sets it according to globalFlags/cmdFlags.
|
||||
func fakeImageDestOptions(t *testing.T, flagPrefix string, useDeprecatedTLSVerify bool,
|
||||
globalFlags []string, cmdFlags []string,
|
||||
) *imageDestOptions {
|
||||
globalFlags []string, cmdFlags []string) *imageDestOptions {
|
||||
globalOpts, cmd := fakeGlobalOptions(t, globalFlags)
|
||||
sharedFlags, sharedOpts := sharedImageFlags()
|
||||
var deprecatedTLSVerifyFlag pflag.FlagSet
|
||||
@@ -391,8 +389,7 @@ func TestSharedCopyOptionsCopyOptions(t *testing.T) {
|
||||
// to create test keys for that.
|
||||
// This does not test --sign-by-sq-fingerprint, because that needs to be conditional based on buildWithSequoia.
|
||||
{
|
||||
options: []string{
|
||||
"--remove-signatures",
|
||||
options: []string{"--remove-signatures",
|
||||
"--sign-by", "gpgFingerprint",
|
||||
"--format", "oci",
|
||||
"--preserve-digests",
|
||||
@@ -491,31 +488,21 @@ func TestParseManifestFormat(t *testing.T) {
|
||||
expectedManifestType string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
"oci",
|
||||
{"oci",
|
||||
imgspecv1.MediaTypeImageManifest,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"v2s1",
|
||||
false},
|
||||
{"v2s1",
|
||||
manifest.DockerV2Schema1SignedMediaType,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"v2s2",
|
||||
false},
|
||||
{"v2s2",
|
||||
manifest.DockerV2Schema2MediaType,
|
||||
false,
|
||||
},
|
||||
{
|
||||
false},
|
||||
{"",
|
||||
"",
|
||||
true},
|
||||
{"badValue",
|
||||
"",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"badValue",
|
||||
"",
|
||||
true,
|
||||
},
|
||||
true},
|
||||
} {
|
||||
manifestType, err := parseManifestFormat(testCase.formatParam)
|
||||
if testCase.expectErr {
|
||||
@@ -536,37 +523,28 @@ func TestImageOptionsAuthfileOverride(t *testing.T) {
|
||||
expectedAuthfilePath string
|
||||
}{
|
||||
// if there is no prefix, only authfile is allowed.
|
||||
{
|
||||
"",
|
||||
{"",
|
||||
[]string{
|
||||
"--authfile", "/srv/authfile",
|
||||
},
|
||||
"/srv/authfile",
|
||||
},
|
||||
}, "/srv/authfile"},
|
||||
// if authfile and dest-authfile is provided, dest-authfile wins
|
||||
{
|
||||
"dest-",
|
||||
{"dest-",
|
||||
[]string{
|
||||
"--authfile", "/srv/authfile",
|
||||
"--dest-authfile", "/srv/dest-authfile",
|
||||
},
|
||||
"/srv/dest-authfile",
|
||||
}, "/srv/dest-authfile",
|
||||
},
|
||||
// if only the shared authfile is provided, authfile must be present in system context
|
||||
{
|
||||
"dest-",
|
||||
{"dest-",
|
||||
[]string{
|
||||
"--authfile", "/srv/authfile",
|
||||
},
|
||||
"/srv/authfile",
|
||||
}, "/srv/authfile",
|
||||
},
|
||||
// if only the dest authfile is provided, dest-authfile must be present in system context
|
||||
{
|
||||
"dest-",
|
||||
{"dest-",
|
||||
[]string{
|
||||
"--dest-authfile", "/srv/dest-authfile",
|
||||
},
|
||||
"/srv/dest-authfile",
|
||||
}, "/srv/dest-authfile",
|
||||
},
|
||||
} {
|
||||
opts := fakeImageOptions(t, testCase.flagPrefix, false, []string{}, testCase.cmdFlags)
|
||||
|
||||
@@ -214,7 +214,7 @@ Precompute digests to ensure layers are not uploaded that already exist on the d
|
||||
|
||||
**--retry-times**
|
||||
|
||||
The number of times to retry. By default, no retries are attempted.
|
||||
The number of times to retry.
|
||||
|
||||
**--retry-delay**
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ Bearer token for accessing the registry.
|
||||
|
||||
**--retry-times**
|
||||
|
||||
The number of times to retry. By default, no retries are attempted.
|
||||
The number of times to retry.
|
||||
|
||||
**--retry-delay**
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ Registry token for accessing the registry.
|
||||
|
||||
**--retry-times**
|
||||
|
||||
The number of times to retry. By default, no retries are attempted.
|
||||
The number of times to retry.
|
||||
|
||||
**--retry-delay**
|
||||
|
||||
@@ -95,12 +95,6 @@ The password to access the registry.
|
||||
|
||||
Do not list the available tags from the repository in the output. When `true`, the `RepoTags` array will be empty. Defaults to `false`, which includes all available tags.
|
||||
|
||||
**--manifest-digest**=_algorithm_ **EXPERIMENTAL**
|
||||
|
||||
Algorithm to use for computing manifest digest (sha256, sha512); defaults to algorithm used in config digest.
|
||||
|
||||
**Note:** This flag is experimental and its behavior may change in future releases.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
To review information for the image fedora from the docker.io registry:
|
||||
@@ -192,12 +186,6 @@ $ /bin/skopeo inspect --format '{{ .Env }}' docker://registry.access.redhat.com/
|
||||
[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin container=oci]
|
||||
```
|
||||
|
||||
To get the digest using a specific algorithm:
|
||||
```console
|
||||
$ skopeo inspect --manifest-digest=sha512 docker://docker.io/library/alpine:latest --format "Digest: {{.Digest}}"
|
||||
Digest: sha512:5acb33fb56a7791bf0c69d5b19a1c70272148e4107be5261d57305d14e9509792bbca53e5277c456181ecfa1c20ad8427f9b8ba46868020584a819de1128dbd2
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
skopeo(1), skopeo-login(1), docker-login(1), containers-auth.json(5)
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ Bearer token for accessing the registry.
|
||||
|
||||
**--retry-times**
|
||||
|
||||
The number of times to retry. By default, no retries are attempted.
|
||||
The number of times to retry.
|
||||
|
||||
**--retry-delay**
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ Only the first line will be read. A passphrase stored in a file is of questionab
|
||||
|
||||
**--retry-times**
|
||||
|
||||
The number of times to retry. By default, no retries are attempted.
|
||||
The number of times to retry.
|
||||
|
||||
**--retry-delay**
|
||||
|
||||
|
||||
@@ -92,10 +92,6 @@ Path to a policy.json file to use for verifying signatures and deciding whether
|
||||
|
||||
Use registry configuration files in _dir_ (e.g. for container signature storage), overriding the default path.
|
||||
|
||||
**--require-signed**
|
||||
|
||||
Require that any pulled image must be signed regardless of what the default or provided trust policy file says.
|
||||
|
||||
**--tmpdir** _dir_
|
||||
|
||||
Directory used to store temporary files. Defaults to /var/tmp.
|
||||
|
||||
98
go.mod
98
go.mod
@@ -1,9 +1,7 @@
|
||||
module github.com/containers/skopeo
|
||||
|
||||
// Minimum required golang version
|
||||
go 1.24.6
|
||||
|
||||
toolchain go1.24.10
|
||||
go 1.24.2
|
||||
|
||||
// Warning: Ensure the "go" and "toolchain" versions match exactly to prevent unwanted auto-updates
|
||||
|
||||
@@ -15,41 +13,39 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.2-0.20251016170850-26647a49f642
|
||||
github.com/opencontainers/image-tools v1.0.0-rc3
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.podman.io/common v0.66.2-0.20260202154637-0e2aefda57c9
|
||||
go.podman.io/image/v5 v5.38.1-0.20260202154637-0e2aefda57c9
|
||||
go.podman.io/storage v1.61.1-0.20260202154637-0e2aefda57c9
|
||||
golang.org/x/term v0.39.0
|
||||
go.podman.io/common v0.66.0
|
||||
go.podman.io/image/v5 v5.38.0
|
||||
go.podman.io/storage v1.61.0
|
||||
golang.org/x/term v0.36.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
cyphar.com/go-pathrs v0.2.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.17.0 // indirect
|
||||
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.16.0 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||
github.com/docker/docker v28.5.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.4 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
@@ -62,51 +58,53 @@ require (
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.3 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.33 // indirect
|
||||
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mistifyio/go-zfs/v4 v4.0.0 // indirect
|
||||
github.com/mistifyio/go-zfs/v3 v3.1.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/moby/api v1.53.0 // indirect
|
||||
github.com/moby/moby/client v0.2.2 // indirect
|
||||
github.com/moby/sys/mountinfo v0.7.2 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opencontainers/image-spec/schema v0.0.0-20250717171153-ab80ff15c2dd // indirect
|
||||
github.com/opencontainers/runtime-spec v1.3.0 // indirect
|
||||
github.com/opencontainers/selinux v1.13.1 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/opencontainers/runtime-spec v1.2.1 // indirect
|
||||
github.com/opencontainers/selinux v1.12.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/proglottis/gpgme v0.1.6 // indirect
|
||||
github.com/proglottis/gpgme v0.1.5 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect
|
||||
github.com/sigstore/fulcio v1.8.1 // indirect
|
||||
github.com/sigstore/protobuf-specs v0.5.0 // indirect
|
||||
github.com/sigstore/sigstore v1.9.6-0.20251111174640-d8ab8afb1326 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.9.1 // indirect
|
||||
github.com/segmentio/ksuid v1.0.4 // indirect
|
||||
github.com/sigstore/fulcio v1.7.1 // indirect
|
||||
github.com/sigstore/protobuf-specs v0.4.1 // indirect
|
||||
github.com/sigstore/sigstore v1.9.5 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||
github.com/smallstep/pkcs7 v0.1.1 // indirect
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect
|
||||
github.com/sylabs/sif/v2 v2.22.0 // indirect
|
||||
github.com/tchap/go-patricia/v2 v2.3.3 // indirect
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/vbatts/tar-split v0.12.2 // indirect
|
||||
github.com/vbauerster/mpb/v8 v8.11.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
|
||||
google.golang.org/grpc v1.76.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||
github.com/vbauerster/mpb/v8 v8.10.2 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/net v0.45.0 // indirect
|
||||
golang.org/x/oauth2 v0.32.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect
|
||||
google.golang.org/grpc v1.72.2 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
)
|
||||
|
||||
286
go.sum
286
go.sum
@@ -1,9 +1,9 @@
|
||||
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
|
||||
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
@@ -12,27 +12,31 @@ github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1o
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.17.0 h1:+TyQIsR/zSFI1Rm31EQBwpAA1ovYgIKHy7kctL3sLcE=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.17.0/go.mod h1:s06tWAiJcXQo9/8AReBCIo/QxcXFZ2n4qfsRnpl71SM=
|
||||
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA=
|
||||
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
|
||||
github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM=
|
||||
github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ=
|
||||
github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow=
|
||||
github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q=
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -41,25 +45,29 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/cli v29.1.5+incompatible h1:GckbANUt3j+lsnQ6eCcQd70mNSOismSHWt8vk2AX8ao=
|
||||
github.com/docker/cli v29.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v28.5.1+incompatible h1:ESutzBALAD6qyCLqbQSEf1a/U8Ybms5agw59yGVc+yY=
|
||||
github.com/docker/cli v28.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
||||
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
|
||||
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI=
|
||||
github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
|
||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -69,6 +77,8 @@ github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
|
||||
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
@@ -87,6 +97,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
@@ -97,11 +109,13 @@ github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVU
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
|
||||
github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
@@ -109,40 +123,47 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ=
|
||||
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
|
||||
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
||||
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mistifyio/go-zfs/v4 v4.0.0 h1:sU0+5dX45tdDK5xNZ3HBi95nxUc48FS92qbIZEvpAg4=
|
||||
github.com/mistifyio/go-zfs/v4 v4.0.0/go.mod h1:weotFtXTHvBwhr9Mv96KYnDkTPBOHFUbm9cBmQpesL0=
|
||||
github.com/mistifyio/go-zfs/v3 v3.1.0 h1:FZaylcg0hjUp27i23VcJJQiuBeAZjrC8lPqCGM1CopY=
|
||||
github.com/mistifyio/go-zfs/v3 v3.1.0/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=
|
||||
github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/moby/moby/client v0.2.2 h1:Pt4hRMCAIlyjL3cr8M5TrXCwKzguebPAc2do2ur7dEM=
|
||||
github.com/moby/moby/client v0.2.2/go.mod h1:2EkIPVNCqR05CMIzL1mfA07t0HvVUUOl85pasRz/GmQ=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
|
||||
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
|
||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE=
|
||||
github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
|
||||
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE=
|
||||
github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.2-0.20251016170850-26647a49f642 h1:BNZwTO1e0QJV7HVGz/Qw/tyOE/GnooRmuy6qZnhNGCE=
|
||||
@@ -151,43 +172,56 @@ github.com/opencontainers/image-spec/schema v0.0.0-20250717171153-ab80ff15c2dd h
|
||||
github.com/opencontainers/image-spec/schema v0.0.0-20250717171153-ab80ff15c2dd/go.mod h1:vPOv9cXqxB6ycHY5iVwqL4rkYbwRh46GZj13CfkZ6As=
|
||||
github.com/opencontainers/image-tools v1.0.0-rc3 h1:ZR837lBIxq6mmwEqfYrbLMuf75eBSHhccVHy6lsBeM4=
|
||||
github.com/opencontainers/image-tools v1.0.0-rc3/go.mod h1:A9btVpZLzttF4iFaKNychhPyrhfOjJ1OF5KrA8GcLj4=
|
||||
github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg=
|
||||
github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE=
|
||||
github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
|
||||
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
|
||||
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/proglottis/gpgme v0.1.6 h1:8WpQ8VWggLdxkuTnW+sZ1r1t92XBNd8GZNDhQ4Rz+98=
|
||||
github.com/proglottis/gpgme v0.1.6/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/proglottis/gpgme v0.1.5 h1:KCGyOw8sQ+SI96j6G8D8YkOGn+1TwbQTT9/zQXoVlz0=
|
||||
github.com/proglottis/gpgme v0.1.5/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
|
||||
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
|
||||
github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sigstore/fulcio v1.8.1 h1:PmoQv3XmhjR2BWFWw5LcMUXJPmhyizOIL7HeYnpio58=
|
||||
github.com/sigstore/fulcio v1.8.1/go.mod h1:7tP3KW9eCGlPYRj5N4MSuUOat7CkeIHuXZ2jAUQ+Rwc=
|
||||
github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY=
|
||||
github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc=
|
||||
github.com/sigstore/sigstore v1.9.6-0.20251111174640-d8ab8afb1326 h1:s39MsSDVn8LhePV5adidcOjjKHaplLxpHM1mvbC24l4=
|
||||
github.com/sigstore/sigstore v1.9.6-0.20251111174640-d8ab8afb1326/go.mod h1:xSCb7eki7lCdi+mNh4I4MVpKPP2cWGtDYmSPPmX/K70=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU=
|
||||
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sigstore/fulcio v1.7.1 h1:RcoW20Nz49IGeZyu3y9QYhyyV3ZKQ85T+FXPKkvE+aQ=
|
||||
github.com/sigstore/fulcio v1.7.1/go.mod h1:7lYY+hsd8Dt+IvKQRC+KEhWpCZ/GlmNvwIa5JhypMS8=
|
||||
github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc=
|
||||
github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc=
|
||||
github.com/sigstore/sigstore v1.9.5 h1:Wm1LT9yF4LhQdEMy5A2JeGRHTrAWGjT3ubE5JUSrGVU=
|
||||
github.com/sigstore/sigstore v1.9.5/go.mod h1:VtxgvGqCmEZN9X2zhFSOkfXxvKUjpy8RpUW39oCtoII=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smallstep/pkcs7 v0.1.1 h1:x+rPdt2W088V9Vkjho4KtoggyktZJlMduZAtRHm68LU=
|
||||
github.com/smallstep/pkcs7 v0.1.1/go.mod h1:dL6j5AIz9GHjVEBTXtW+QliALcgM19RtXaTeyxI+AfA=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -195,19 +229,22 @@ github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLy
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/sylabs/sif/v2 v2.22.0 h1:Y+xXufp4RdgZe02SR3nWEg7S6q4tPWN237WHYzkDSKA=
|
||||
github.com/sylabs/sif/v2 v2.22.0/go.mod h1:W1XhWTmG1KcG7j5a3KSYdMcUIFvbs240w/MMVW627hs=
|
||||
github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc=
|
||||
github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
|
||||
github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
|
||||
github.com/vbauerster/mpb/v8 v8.11.3 h1:iniBmO4ySXCl4gVdmJpgrtormH5uvjpxcx/dMyVU9Jw=
|
||||
github.com/vbauerster/mpb/v8 v8.11.3/go.mod h1:n9M7WbP0NFjpgKS5XdEC3tMRgZTNM/xtC8zWGkiMuy0=
|
||||
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
|
||||
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
|
||||
github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM=
|
||||
github.com/vbauerster/mpb/v8 v8.10.2/go.mod h1:+Ja4P92E3/CorSZgfDtK46D7AVbDqmBQRTmyTqPElo0=
|
||||
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
|
||||
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
|
||||
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
|
||||
@@ -219,26 +256,34 @@ github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3R
|
||||
github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=
|
||||
github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.podman.io/common v0.66.2-0.20260202154637-0e2aefda57c9 h1:iLJYwHeJ548eWlrOgFFnE2QW7A4SHgTHjw+4QJqJ0kY=
|
||||
go.podman.io/common v0.66.2-0.20260202154637-0e2aefda57c9/go.mod h1:+4bMKfBbfK+qgURkCj6vUtghP5ASjsWyYDI/udLOKxk=
|
||||
go.podman.io/image/v5 v5.38.1-0.20260202154637-0e2aefda57c9 h1:RFtNtYD33WvYJKAoCzONX2AjP7Ey1MtikfKfJ+dcWCk=
|
||||
go.podman.io/image/v5 v5.38.1-0.20260202154637-0e2aefda57c9/go.mod h1:imQIBRN6114qH01ttrueVkVCHj28jhsiN7Yubh0CzGc=
|
||||
go.podman.io/storage v1.61.1-0.20260202154637-0e2aefda57c9 h1:ab5KO2VjxG/VsARN5gBsQoCuQvJr1MYSYf50hpn1ROI=
|
||||
go.podman.io/storage v1.61.1-0.20260202154637-0e2aefda57c9/go.mod h1:yuLB1ikwsdGrGqSGBWv7fMbOeHupCaMn5iJ1biqxrpI=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.podman.io/common v0.66.0 h1:KElE3HKLFdMdJL+jv5ExBiX2Dh4Qcv8ovmzaBGRsyZM=
|
||||
go.podman.io/common v0.66.0/go.mod h1:aNd2a0S7pY+fx1X5kpQYuF4hbwLU8ZOccuVrhu7h1Xc=
|
||||
go.podman.io/image/v5 v5.38.0 h1:aUKrCANkPvze1bnhLJsaubcfz0d9v/bSDLnwsXJm6G4=
|
||||
go.podman.io/image/v5 v5.38.0/go.mod h1:hSIoIUzgBnmc4DjoIdzk63aloqVbD7QXDMkSE/cvG90=
|
||||
go.podman.io/storage v1.61.0 h1:5hD/oyRYt1f1gxgvect+8syZBQhGhV28dCw2+CZpx0Q=
|
||||
go.podman.io/storage v1.61.0/go.mod h1:A3UBK0XypjNZ6pghRhuxg62+2NIm5lcUGv/7XyMhMUI=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -247,15 +292,15 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
@@ -264,10 +309,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -275,22 +320,22 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -300,8 +345,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
@@ -311,34 +356,33 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e h1:UdXH7Kzbj+Vzastr5nVfccbmFsmYNygVLSPk1pEfDoY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
|
||||
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
|
||||
27
hack/validate-gofmt.sh
Executable file
27
hack/validate-gofmt.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
IFS=$'\n'
|
||||
files=( $(find . -name '*.go' | grep -v '^./vendor/' | sort || true) )
|
||||
unset IFS
|
||||
|
||||
badFiles=()
|
||||
for f in "${files[@]}"; do
|
||||
if [ "$(gofmt -s -l < $f)" ]; then
|
||||
badFiles+=( "$f" )
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#badFiles[@]} -eq 0 ]; then
|
||||
echo 'Congratulations! All Go source files are properly formatted.'
|
||||
else
|
||||
{
|
||||
echo "These files are not properly gofmt'd:"
|
||||
for f in "${badFiles[@]}"; do
|
||||
echo " - $f"
|
||||
done
|
||||
echo
|
||||
echo 'Please reformat the above files using "gofmt -s -w" and commit the result.'
|
||||
echo
|
||||
} >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,9 +1,7 @@
|
||||
package main
|
||||
|
||||
const (
|
||||
blockedRegistriesConf = "./fixtures/blocked-registries.conf"
|
||||
blockedErrorRegex = `.*registry registry-blocked.com is blocked in .*`
|
||||
)
|
||||
const blockedRegistriesConf = "./fixtures/blocked-registries.conf"
|
||||
const blockedErrorRegex = `.*registry registry-blocked.com is blocked in .*`
|
||||
|
||||
func (s *skopeoSuite) TestCopyBlockedSource() {
|
||||
t := s.T()
|
||||
|
||||
@@ -26,10 +26,8 @@ type skopeoSuite struct {
|
||||
regV2WithAuth *testRegistryV2
|
||||
}
|
||||
|
||||
var (
|
||||
_ = suite.SetupAllSuite(&skopeoSuite{})
|
||||
_ = suite.TearDownAllSuite(&skopeoSuite{})
|
||||
)
|
||||
var _ = suite.SetupAllSuite(&skopeoSuite{})
|
||||
var _ = suite.TearDownAllSuite(&skopeoSuite{})
|
||||
|
||||
func (s *skopeoSuite) SetupSuite() {
|
||||
t := s.T()
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -43,17 +42,14 @@ func TestCopy(t *testing.T) {
|
||||
|
||||
type copySuite struct {
|
||||
suite.Suite
|
||||
cluster *openshiftCluster
|
||||
registry *testRegistryV2
|
||||
s1Registry *testRegistryV2
|
||||
gpgHome string
|
||||
fingerprint string
|
||||
cluster *openshiftCluster
|
||||
registry *testRegistryV2
|
||||
s1Registry *testRegistryV2
|
||||
gpgHome string
|
||||
}
|
||||
|
||||
var (
|
||||
_ = suite.SetupAllSuite(©Suite{})
|
||||
_ = suite.TearDownAllSuite(©Suite{})
|
||||
)
|
||||
var _ = suite.SetupAllSuite(©Suite{})
|
||||
var _ = suite.TearDownAllSuite(©Suite{})
|
||||
|
||||
func (s *copySuite) SetupSuite() {
|
||||
t := s.T()
|
||||
@@ -89,15 +85,9 @@ func (s *copySuite) SetupSuite() {
|
||||
|
||||
out := combinedOutputOfCommand(t, gpgBinary, "--armor", "--export", fmt.Sprintf("%s@example.com", key))
|
||||
err := os.WriteFile(filepath.Join(s.gpgHome, fmt.Sprintf("%s-pubkey.gpg", key)),
|
||||
[]byte(out), 0o600)
|
||||
[]byte(out), 0600)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Get fingerprint for the personal key (used by some tests)
|
||||
lines, err := exec.Command(gpgBinary, "--homedir", s.gpgHome, "--with-colons", "--no-permission-warning", "--fingerprint", "personal@example.com").Output()
|
||||
require.NoError(t, err)
|
||||
s.fingerprint, err = findFingerprint(lines)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (s *copySuite) TearDownSuite() {
|
||||
@@ -551,9 +541,9 @@ func (s *copySuite) TestCopyEncryption() {
|
||||
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(keysDir+"/private.key", privateKeyBytes, 0o644)
|
||||
err = os.WriteFile(keysDir+"/private.key", privateKeyBytes, 0644)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(keysDir+"/public.key", publicKeyBytes, 0o644)
|
||||
err = os.WriteFile(keysDir+"/public.key", publicKeyBytes, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// We can either perform encryption or decryption on the image.
|
||||
@@ -578,7 +568,7 @@ func (s *copySuite) TestCopyEncryption() {
|
||||
invalidPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
require.NoError(t, err)
|
||||
invalidPrivateKeyBytes := x509.MarshalPKCS1PrivateKey(invalidPrivateKey)
|
||||
err = os.WriteFile(keysDir+"/invalid_private.key", invalidPrivateKeyBytes, 0o644)
|
||||
err = os.WriteFile(keysDir+"/invalid_private.key", invalidPrivateKeyBytes, 0644)
|
||||
require.NoError(t, err)
|
||||
assertSkopeoFails(t, ".*no suitable key unwrapper found or none of the private keys could be used for decryption.*",
|
||||
"copy", "--decryption-key", keysDir+"/invalid_private.key",
|
||||
@@ -614,6 +604,7 @@ func (s *copySuite) TestCopyEncryption() {
|
||||
|
||||
// After successful decryption we should find the gzipped layers from the nginx image
|
||||
matchLayerBlobBinaryType(t, partiallyDecryptedImgDir+"/blobs/sha256", "application/x-gzip", 3)
|
||||
|
||||
}
|
||||
|
||||
func matchLayerBlobBinaryType(t *testing.T, ociImageDirPath string, contentType string, matchCount int) {
|
||||
@@ -789,10 +780,10 @@ func (s *copySuite) TestCopySignatures() {
|
||||
// Verify that mis-signed images are rejected
|
||||
assertSkopeoSucceeds(t, "", "--tls-verify=false", "copy", "atomic:localhost:5006/myns/personal:personal", "atomic:localhost:5006/myns/official:attack")
|
||||
assertSkopeoSucceeds(t, "", "--tls-verify=false", "copy", "atomic:localhost:5006/myns/official:official", "atomic:localhost:5006/myns/personal:attack")
|
||||
// "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" or "Missing key $fingerprint" by Sequoia.
|
||||
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key).*",
|
||||
// "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" by Sequoia.
|
||||
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key:).*",
|
||||
"--tls-verify=false", "--policy", policy, "copy", "atomic:localhost:5006/myns/personal:attack", dirDest)
|
||||
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key).*",
|
||||
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key:).*",
|
||||
"--tls-verify=false", "--policy", policy, "copy", "atomic:localhost:5006/myns/official:attack", dirDest)
|
||||
|
||||
// Verify that signed identity is verified.
|
||||
@@ -805,8 +796,8 @@ func (s *copySuite) TestCopySignatures() {
|
||||
|
||||
// Verify that cosigning requirements are enforced
|
||||
assertSkopeoSucceeds(t, "", "--tls-verify=false", "copy", "atomic:localhost:5006/myns/official:official", "atomic:localhost:5006/myns/cosigned:cosigned")
|
||||
// "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" or "Missing key $fingerprint" by Sequoia.
|
||||
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key).*",
|
||||
// "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" by Sequoia.
|
||||
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key:).*",
|
||||
"--tls-verify=false", "--policy", policy, "copy", "atomic:localhost:5006/myns/cosigned:cosigned", dirDest)
|
||||
|
||||
assertSkopeoSucceeds(t, "", "--tls-verify=false", "copy", "--sign-by", "personal@example.com", "atomic:localhost:5006/myns/official:official", "atomic:localhost:5006/myns/cosigned:cosigned")
|
||||
@@ -827,7 +818,7 @@ func (s *copySuite) TestCopyDirSignatures() {
|
||||
topDirDest := "dir:" + topDir
|
||||
|
||||
for _, suffix := range []string{"/dir1", "/dir2", "/restricted/personal", "/restricted/official", "/restricted/badidentity", "/dest"} {
|
||||
err := os.MkdirAll(topDir+suffix, 0o755)
|
||||
err := os.MkdirAll(topDir+suffix, 0755)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -851,8 +842,8 @@ func (s *copySuite) TestCopyDirSignatures() {
|
||||
// Verify that correct images are accepted
|
||||
assertSkopeoSucceeds(t, "", "--policy", policy, "copy", topDirDest+"/restricted/official", topDirDest+"/dest")
|
||||
// ... and that mis-signed images are rejected.
|
||||
// "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" or "Missing key $fingerprint" by Sequoia.
|
||||
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key).*",
|
||||
// "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" by Sequoia.
|
||||
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key:).*",
|
||||
"--policy", policy, "copy", topDirDest+"/restricted/personal", topDirDest+"/dest")
|
||||
|
||||
// Verify that the signed identity is verified.
|
||||
@@ -909,7 +900,7 @@ func (s *copySuite) TestCopyCompression() {
|
||||
{"uncompressed-image-s2", "atomic:localhost:5000/myns/compression:s2"},
|
||||
} {
|
||||
dir := filepath.Join(topDir, fmt.Sprintf("case%d", i))
|
||||
err := os.MkdirAll(dir, 0o755)
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertSkopeoSucceeds(t, "", "--tls-verify=false", "copy", "dir:fixtures/"+c.fixture, c.remote)
|
||||
@@ -962,7 +953,7 @@ func (s *copySuite) TestCopyDockerLookaside() {
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
copyDest := filepath.Join(tmpDir, "dest")
|
||||
err = os.Mkdir(copyDest, 0o755)
|
||||
err = os.Mkdir(copyDest, 0755)
|
||||
require.NoError(t, err)
|
||||
dirDest := "dir:" + copyDest
|
||||
plainLookaside := filepath.Join(tmpDir, "lookaside")
|
||||
@@ -976,7 +967,7 @@ func (s *copySuite) TestCopyDockerLookaside() {
|
||||
|
||||
policy := s.policyFixture(nil)
|
||||
registriesDir := filepath.Join(tmpDir, "registries.d")
|
||||
err = os.Mkdir(registriesDir, 0o755)
|
||||
err = os.Mkdir(registriesDir, 0755)
|
||||
require.NoError(t, err)
|
||||
registriesFile := fileFromFixture(t, "fixtures/registries.yaml",
|
||||
map[string]string{"@lookaside@": plainLookaside, "@split-staging@": splitLookasideStaging, "@split-read@": splitLookasideReadServer.URL})
|
||||
@@ -1029,7 +1020,7 @@ func (s *copySuite) TestCopyAtomicExtension() {
|
||||
|
||||
topDir := t.TempDir()
|
||||
for _, subdir := range []string{"dirAA", "dirAD", "dirDA", "dirDD", "registries.d"} {
|
||||
err := os.MkdirAll(filepath.Join(topDir, subdir), 0o755)
|
||||
err := os.MkdirAll(filepath.Join(topDir, subdir), 0755)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
registriesDir := filepath.Join(topDir, "registries.d")
|
||||
@@ -1222,7 +1213,7 @@ func (s *copySuite) TestCopyPreserveDigests() {
|
||||
func (s *copySuite) testCopySchemaConversionRegistries(t *testing.T, schema1Registry, schema2Registry string) {
|
||||
topDir := t.TempDir()
|
||||
for _, subdir := range []string{"input1", "input2", "dest2"} {
|
||||
err := os.MkdirAll(filepath.Join(topDir, subdir), 0o755)
|
||||
err := os.MkdirAll(filepath.Join(topDir, subdir), 0755)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
input1Dir := filepath.Join(topDir, "input1")
|
||||
@@ -1293,87 +1284,3 @@ func (s *copySuite) TestCopyFailsWhenReferenceIsInvalid() {
|
||||
t := s.T()
|
||||
assertSkopeoFails(t, `.*Invalid image name.*`, "copy", "unknown:transport", "unknown:test")
|
||||
}
|
||||
|
||||
func (s *copySuite) TestInsecurePolicyAndRequireSignedConflict() {
|
||||
t := s.T()
|
||||
assertSkopeoFails(t, ".*--insecure-policy and --require-signed are mutually exclusive.*",
|
||||
"--insecure-policy", "--require-signed", "inspect", "dir:/nonexistent")
|
||||
}
|
||||
|
||||
func (s *copySuite) TestRequireSignedAcceptsSignedImage() {
|
||||
t := s.T()
|
||||
mech, err := signature.NewGPGSigningMechanism()
|
||||
require.NoError(t, err)
|
||||
defer mech.Close()
|
||||
if err := mech.SupportsSigning(); err != nil {
|
||||
t.Skipf("Signing not supported: %v", err)
|
||||
}
|
||||
|
||||
srcDir := t.TempDir()
|
||||
|
||||
// get an image to work with
|
||||
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3",
|
||||
testFQIN64, "dir:"+srcDir)
|
||||
|
||||
// first, sanity-check that without --require-signed, we can copy it since by default, `dir:` is insecureAcceptAnything
|
||||
destDir1 := t.TempDir()
|
||||
assertSkopeoSucceeds(t, "", "copy", "dir:"+srcDir, "dir:"+destDir1)
|
||||
|
||||
// now verify that copying fails with --require-signed
|
||||
destDir2 := t.TempDir()
|
||||
assertSkopeoFails(t, ".*Source image rejected: No signature verification policy found for image.*",
|
||||
"--require-signed", "copy",
|
||||
"dir:"+srcDir, "dir:"+destDir2)
|
||||
|
||||
// sign the image
|
||||
manifestPath := filepath.Join(srcDir, "manifest.json")
|
||||
signaturePath := filepath.Join(srcDir, "signature-1")
|
||||
dockerReference := "localhost/test:latest"
|
||||
|
||||
assertSkopeoSucceeds(t, "", "standalone-sign",
|
||||
"-o", signaturePath,
|
||||
manifestPath, dockerReference, s.fingerprint)
|
||||
|
||||
// sanity-check signature file is there
|
||||
_, err = os.Stat(signaturePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create a basic policy that requires signatures
|
||||
policy := map[string]any{
|
||||
"default": []map[string]any{{
|
||||
"type": "signedBy",
|
||||
"keyType": "GPGKeys",
|
||||
"keyPath": filepath.Join(s.gpgHome, "personal-pubkey.gpg"),
|
||||
"signedIdentity": map[string]any{
|
||||
"type": "exactRepository",
|
||||
"dockerRepository": dockerReference,
|
||||
},
|
||||
}},
|
||||
}
|
||||
policyJSON, err := json.Marshal(policy)
|
||||
require.NoError(t, err)
|
||||
|
||||
policyFile, err := os.CreateTemp("", "policy-*.json")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { os.Remove(policyFile.Name()) })
|
||||
_, err = policyFile.Write(policyJSON)
|
||||
require.NoError(t, err)
|
||||
err = policyFile.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
// now copying with --require-signed should pass
|
||||
destDir3 := t.TempDir()
|
||||
assertSkopeoSucceeds(t, "", "--policy", policyFile.Name(), "--require-signed", "copy",
|
||||
"dir:"+srcDir, "dir:"+destDir3)
|
||||
|
||||
// Delete the signature and sanity-check that copying fails. This doesn't
|
||||
// strictly test --require-signed, but rather the PolicyRequirements logic, but
|
||||
// it makes the test feel complete.
|
||||
err = os.Remove(signaturePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
destDir4 := t.TempDir()
|
||||
assertSkopeoFails(t, ".*Source image rejected: A signature was required, but no signature exists.*",
|
||||
"--policy", policyFile.Name(), "--require-signed", "copy",
|
||||
"dir:"+srcDir, "dir:"+destDir4)
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ func (cluster *openshiftCluster) startRegistry(t *testing.T) {
|
||||
cluster.processes = append(cluster.processes, cluster.startRegistryProcess(t, 5006, schema2Config))
|
||||
}
|
||||
|
||||
// ocLoginToProject runs (oc login) and (oc new-project) on the cluster, or terminates on failure.
|
||||
// ocLogin runs (oc login) and (oc new-project) on the cluster, or terminates on failure.
|
||||
func (cluster *openshiftCluster) ocLoginToProject(t *testing.T) {
|
||||
t.Logf("oc login")
|
||||
cmd := cluster.clusterCmd(nil, "oc", "login", "--certificate-authority=openshift.local.config/master/ca.crt", "-u", "myuser", "-p", "mypw", "https://localhost:8443")
|
||||
@@ -223,7 +223,7 @@ func (cluster *openshiftCluster) ocLoginToProject(t *testing.T) {
|
||||
// We do not run (docker login) directly, because that requires a running daemon and a docker package.
|
||||
func (cluster *openshiftCluster) dockerLogin(t *testing.T) {
|
||||
cluster.dockerDir = filepath.Join(homedir.Get(), ".docker")
|
||||
err := os.MkdirAll(cluster.dockerDir, 0o700)
|
||||
err := os.MkdirAll(cluster.dockerDir, 0700)
|
||||
require.NoError(t, err)
|
||||
|
||||
out := combinedOutputOfCommand(t, "oc", "config", "view", "-o", "json", "-o", "jsonpath={.users[*].user.token}")
|
||||
@@ -237,7 +237,7 @@ func (cluster *openshiftCluster) dockerLogin(t *testing.T) {
|
||||
}`, port, authValue))
|
||||
}
|
||||
configJSON := `{"auths": {` + strings.Join(auths, ",") + `}}`
|
||||
err = os.WriteFile(filepath.Join(cluster.dockerDir, "config.json"), []byte(configJSON), 0o600)
|
||||
err = os.WriteFile(filepath.Join(cluster.dockerDir, "config.json"), []byte(configJSON), 0600)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -224,6 +224,7 @@ func (p *proxy) callGetRawBlob(args []any) (rval any, buf []byte, err error) {
|
||||
content: buf,
|
||||
err: err,
|
||||
}
|
||||
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
|
||||
@@ -70,7 +70,7 @@ compatibility:
|
||||
username = "testuser"
|
||||
password = "testpassword"
|
||||
email = "test@test.org"
|
||||
if err := os.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0o644)); err != nil {
|
||||
if err := os.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
htpasswd = fmt.Sprintf(`auth:
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -26,6 +29,16 @@ type signingSuite struct {
|
||||
|
||||
var _ = suite.SetupAllSuite(&signingSuite{})
|
||||
|
||||
func findFingerprint(lineBytes []byte) (string, error) {
|
||||
for line := range bytes.SplitSeq(lineBytes, []byte{'\n'}) {
|
||||
fields := strings.Split(string(line), ":")
|
||||
if len(fields) >= 10 && fields[0] == "fpr" {
|
||||
return fields[9], nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("No fingerprint found")
|
||||
}
|
||||
|
||||
func (s *signingSuite) SetupSuite() {
|
||||
t := s.T()
|
||||
_, err := exec.LookPath(skopeoBinary)
|
||||
|
||||
@@ -46,10 +46,8 @@ type syncSuite struct {
|
||||
registry *testRegistryV2
|
||||
}
|
||||
|
||||
var (
|
||||
_ = suite.SetupAllSuite(&syncSuite{})
|
||||
_ = suite.TearDownAllSuite(&syncSuite{})
|
||||
)
|
||||
var _ = suite.SetupAllSuite(&syncSuite{})
|
||||
var _ = suite.TearDownAllSuite(&syncSuite{})
|
||||
|
||||
func (s *syncSuite) SetupSuite() {
|
||||
t := s.T()
|
||||
@@ -94,7 +92,7 @@ func (s *syncSuite) SetupSuite() {
|
||||
|
||||
out := combinedOutputOfCommand(t, gpgBinary, "--armor", "--export", fmt.Sprintf("%s@example.com", key))
|
||||
err := os.WriteFile(filepath.Join(gpgHome, fmt.Sprintf("%s-pubkey.gpg", key)),
|
||||
[]byte(out), 0o600)
|
||||
[]byte(out), 0600)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
@@ -228,16 +226,16 @@ func (s *syncSuite) TestDirIsNotOverwritten() {
|
||||
// make a copy of the image in the local registry
|
||||
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--dest-tls-verify=false", "docker://"+image, "docker://"+path.Join(v2DockerRegistryURL, reference.Path(imageRef.DockerReference())))
|
||||
|
||||
// sync upstream image to dir, not scoped
|
||||
//sync upstream image to dir, not scoped
|
||||
dir1 := t.TempDir()
|
||||
assertSkopeoSucceeds(t, "", "sync", "--src", "docker", "--dest", "dir", image, dir1)
|
||||
_, err = os.Stat(path.Join(dir1, path.Base(imagePath), "manifest.json"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// sync local registry image to dir, not scoped
|
||||
//sync local registry image to dir, not scoped
|
||||
assertSkopeoFails(t, ".*Refusing to overwrite destination directory.*", "sync", "--src-tls-verify=false", "--src", "docker", "--dest", "dir", path.Join(v2DockerRegistryURL, reference.Path(imageRef.DockerReference())), dir1)
|
||||
|
||||
// sync local registry image to dir, scoped
|
||||
//sync local registry image to dir, scoped
|
||||
imageRef, err = docker.ParseReference(fmt.Sprintf("//%s", path.Join(v2DockerRegistryURL, reference.Path(imageRef.DockerReference()))))
|
||||
require.NoError(t, err)
|
||||
imagePath = imageRef.DockerReference().String()
|
||||
@@ -292,7 +290,7 @@ func (s *syncSuite) TestYamlUntagged() {
|
||||
|
||||
// sync to the local registry
|
||||
yamlFile := path.Join(tmpDir, "registries.yaml")
|
||||
err = os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
|
||||
err = os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
|
||||
require.NoError(t, err)
|
||||
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--src", "yaml", "--dest", "docker", "--dest-tls-verify=false", yamlFile, v2DockerRegistryURL)
|
||||
// sync back from local registry to a folder
|
||||
@@ -304,7 +302,7 @@ func (s *syncSuite) TestYamlUntagged() {
|
||||
%s: []
|
||||
`, v2DockerRegistryURL, imagePath)
|
||||
|
||||
err = os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
|
||||
err = os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
|
||||
require.NoError(t, err)
|
||||
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
|
||||
|
||||
@@ -331,11 +329,11 @@ registry.k8s.io:
|
||||
pause: ^[12]\.0$ # regex string test
|
||||
`
|
||||
// the ↑ regex strings always matches only 2 images
|
||||
nTags := 2
|
||||
var nTags = 2
|
||||
assert.NotZero(t, nTags)
|
||||
|
||||
yamlFile := path.Join(tmpDir, "registries.yaml")
|
||||
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
|
||||
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
|
||||
require.NoError(t, err)
|
||||
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
|
||||
assertNumberOfManifestsInSubdirs(t, dir1, nTags)
|
||||
@@ -353,7 +351,7 @@ registry.k8s.io:
|
||||
- sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610
|
||||
`
|
||||
yamlFile := path.Join(tmpDir, "registries.yaml")
|
||||
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
|
||||
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
|
||||
require.NoError(t, err)
|
||||
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
|
||||
assertNumberOfManifestsInSubdirs(t, dir1, 1)
|
||||
@@ -392,7 +390,7 @@ quay.io:
|
||||
assert.NotZero(t, nTags)
|
||||
|
||||
yamlFile := path.Join(tmpDir, "registries.yaml")
|
||||
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
|
||||
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
|
||||
require.NoError(t, err)
|
||||
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
|
||||
assertNumberOfManifestsInSubdirs(t, dir1, nTags)
|
||||
@@ -443,13 +441,14 @@ func (s *syncSuite) TestYamlTLSVerify() {
|
||||
for _, cfg := range testCfg {
|
||||
yamlConfig := fmt.Sprintf(yamlTemplate, v2DockerRegistryURL, cfg.tlsVerify, image, tag)
|
||||
yamlFile := path.Join(tmpDir, "registries.yaml")
|
||||
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
|
||||
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg.checker(t, cfg.msg, "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
|
||||
os.Remove(yamlFile)
|
||||
os.RemoveAll(dir1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *syncSuite) TestSyncManifestOutput() {
|
||||
@@ -460,7 +459,7 @@ func (s *syncSuite) TestSyncManifestOutput() {
|
||||
destDir2 := filepath.Join(tmpDir, "dest2")
|
||||
destDir3 := filepath.Join(tmpDir, "dest3")
|
||||
|
||||
// Split image:tag path from image URI for manifest comparison
|
||||
//Split image:tag path from image URI for manifest comparison
|
||||
imageDir := pullableTaggedImage[strings.LastIndex(pullableTaggedImage, "/")+1:]
|
||||
|
||||
assertSkopeoSucceeds(t, "", "sync", "--format=oci", "--all", "--src", "docker", "--dest", "dir", pullableTaggedImage, destDir1)
|
||||
@@ -513,14 +512,14 @@ func (s *syncSuite) TestDir2DockerTagged() {
|
||||
image := pullableRepoWithLatestTag
|
||||
|
||||
dir1 := path.Join(tmpDir, "dir1")
|
||||
err := os.Mkdir(dir1, 0o755)
|
||||
err := os.Mkdir(dir1, 0755)
|
||||
require.NoError(t, err)
|
||||
dir2 := path.Join(tmpDir, "dir2")
|
||||
err = os.Mkdir(dir2, 0o755)
|
||||
err = os.Mkdir(dir2, 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create leading dirs
|
||||
err = os.MkdirAll(path.Dir(path.Join(dir1, image)), 0o755)
|
||||
err = os.MkdirAll(path.Dir(path.Join(dir1, image)), 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// copy docker => dir
|
||||
@@ -532,7 +531,7 @@ func (s *syncSuite) TestDir2DockerTagged() {
|
||||
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--dest-tls-verify=false", "--src", "dir", "--dest", "docker", dir1, v2DockerRegistryURL)
|
||||
|
||||
// create leading dirs
|
||||
err = os.MkdirAll(path.Dir(path.Join(dir2, image)), 0o755)
|
||||
err = os.MkdirAll(path.Dir(path.Join(dir2, image)), 0755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// copy docker => dir
|
||||
@@ -572,11 +571,11 @@ func (s *syncSuite) TestFailsWithDockerSourceNoRegistry() {
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// untagged
|
||||
//untagged
|
||||
assertSkopeoFails(t, ".*StatusCode: 404.*",
|
||||
"sync", "--scoped", "--src", "docker", "--dest", "dir", regURL, tmpDir)
|
||||
|
||||
// tagged
|
||||
//tagged
|
||||
assertSkopeoFails(t, ".*StatusCode: 404.*",
|
||||
"sync", "--scoped", "--src", "docker", "--dest", "dir", regURL+":thetag", tmpDir)
|
||||
}
|
||||
@@ -586,11 +585,11 @@ func (s *syncSuite) TestFailsWithDockerSourceUnauthorized() {
|
||||
const repo = "privateimagenamethatshouldnotbepublic"
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// untagged
|
||||
//untagged
|
||||
assertSkopeoFails(t, ".*requested access to the resource is denied.*",
|
||||
"sync", "--scoped", "--src", "docker", "--dest", "dir", repo, tmpDir)
|
||||
|
||||
// tagged
|
||||
//tagged
|
||||
assertSkopeoFails(t, ".*requested access to the resource is denied.*",
|
||||
"sync", "--scoped", "--src", "docker", "--dest", "dir", repo+":thetag", tmpDir)
|
||||
}
|
||||
@@ -600,11 +599,11 @@ func (s *syncSuite) TestFailsWithDockerSourceNotExisting() {
|
||||
repo := path.Join(v2DockerRegistryURL, "imagedoesnotexist")
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// untagged
|
||||
//untagged
|
||||
assertSkopeoFails(t, ".*repository name not known to registry.*",
|
||||
"sync", "--scoped", "--src-tls-verify=false", "--src", "docker", "--dest", "dir", repo, tmpDir)
|
||||
|
||||
// tagged
|
||||
//tagged
|
||||
assertSkopeoFails(t, ".*reading manifest.*",
|
||||
"sync", "--scoped", "--src-tls-verify=false", "--src", "docker", "--dest", "dir", repo+":thetag", tmpDir)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
@@ -30,22 +29,9 @@ var skopeoBinary = func() string {
|
||||
return "skopeo"
|
||||
}()
|
||||
|
||||
// findFingerprint extracts the GPG key fingerprint from gpg --with-colons output.
|
||||
func findFingerprint(lineBytes []byte) (string, error) {
|
||||
for line := range bytes.SplitSeq(lineBytes, []byte{'\n'}) {
|
||||
fields := strings.Split(string(line), ":")
|
||||
if len(fields) >= 10 && fields[0] == "fpr" {
|
||||
return fields[9], nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("No fingerprint found")
|
||||
}
|
||||
|
||||
const (
|
||||
testFQIN = "docker://quay.io/libpod/busybox" // tag left off on purpose, some tests need to add a special one
|
||||
testFQIN64 = "docker://quay.io/libpod/busybox:amd64"
|
||||
testFQINMultiLayer = "docker://quay.io/libpod/alpine_nginx:latest" // multi-layer
|
||||
)
|
||||
const testFQIN = "docker://quay.io/libpod/busybox" // tag left off on purpose, some tests need to add a special one
|
||||
const testFQIN64 = "docker://quay.io/libpod/busybox:amd64"
|
||||
const testFQINMultiLayer = "docker://quay.io/libpod/alpine_nginx:latest" // multi-layer
|
||||
|
||||
// consumeAndLogOutputStream takes (f, err) from an exec.*Pipe(), and causes all output to it to be logged to t.
|
||||
func consumeAndLogOutputStream(t *testing.T, id string, f io.ReadCloser, err error) {
|
||||
|
||||
@@ -137,7 +137,7 @@ END_PUSH
|
||||
# The table below lists the paths to fetch, and the expected errors (or
|
||||
# none, if we expect them to pass).
|
||||
#
|
||||
# "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" or "Missing key $fingerprint" by Sequoia.
|
||||
# "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" by Sequoia.
|
||||
while read path expected_error; do
|
||||
expected_rc=
|
||||
if [[ -n $expected_error ]]; then
|
||||
@@ -156,7 +156,7 @@ END_PUSH
|
||||
fi
|
||||
done <<END_TESTS
|
||||
/myns/alice:signed
|
||||
/myns/bob:signedbyalice (Invalid GPG signature|Missing key)
|
||||
/myns/bob:signedbyalice (Invalid GPG signature|Missing key:)
|
||||
/myns/alice:unsigned Signature for identity \\\\\\\\"localhost:5000/myns/alice:signed\\\\\\\\" is not accepted
|
||||
/myns/carol:latest Running image docker://localhost:5000/myns/carol:latest is rejected by policy.
|
||||
/open/forall:latest
|
||||
|
||||
43
vendor/cyphar.com/go-pathrs/.golangci.yml
generated
vendored
43
vendor/cyphar.com/go-pathrs/.golangci.yml
generated
vendored
@@ -1,43 +0,0 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# libpathrs: safe path resolution on Linux
|
||||
# Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
# Copyright (C) 2019-2025 SUSE LLC
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
enable:
|
||||
- bidichk
|
||||
- cyclop
|
||||
- errname
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- goconst
|
||||
- godot
|
||||
- gomoddirectives
|
||||
- gosec
|
||||
- mirror
|
||||
- misspell
|
||||
- mnd
|
||||
- nilerr
|
||||
- nilnil
|
||||
- perfsprint
|
||||
- prealloc
|
||||
- reassign
|
||||
- revive
|
||||
- unconvert
|
||||
- unparam
|
||||
- usestdlibvars
|
||||
- wastedassign
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
settings:
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- cyphar.com/go-pathrs
|
||||
14
vendor/cyphar.com/go-pathrs/doc.go
generated
vendored
14
vendor/cyphar.com/go-pathrs/doc.go
generated
vendored
@@ -1,14 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
/*
|
||||
* libpathrs: safe path resolution on Linux
|
||||
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
* Copyright (C) 2019-2025 SUSE LLC
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// Package pathrs provides bindings for libpathrs, a library for safe path
|
||||
// resolution on Linux.
|
||||
package pathrs
|
||||
114
vendor/cyphar.com/go-pathrs/handle_linux.go
generated
vendored
114
vendor/cyphar.com/go-pathrs/handle_linux.go
generated
vendored
@@ -1,114 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
/*
|
||||
* libpathrs: safe path resolution on Linux
|
||||
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
* Copyright (C) 2019-2025 SUSE LLC
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package pathrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"cyphar.com/go-pathrs/internal/fdutils"
|
||||
"cyphar.com/go-pathrs/internal/libpathrs"
|
||||
)
|
||||
|
||||
// Handle is a handle for a path within a given [Root]. This handle references
|
||||
// an already-resolved path which can be used for only one purpose -- to
|
||||
// "re-open" the handle and get an actual [os.File] which can be used for
|
||||
// ordinary operations.
|
||||
//
|
||||
// If you wish to open a file without having an intermediate [Handle] object,
|
||||
// you can try to use [Root.Open] or [Root.OpenFile].
|
||||
//
|
||||
// It is critical that perform all relevant operations through this [Handle]
|
||||
// (rather than fetching the file descriptor yourself with [Handle.IntoRaw]),
|
||||
// because the security properties of libpathrs depend on users doing all
|
||||
// relevant filesystem operations through libpathrs.
|
||||
//
|
||||
// [os.File]: https://pkg.go.dev/os#File
|
||||
type Handle struct {
|
||||
inner *os.File
|
||||
}
|
||||
|
||||
// HandleFromFile creates a new [Handle] from an existing file handle. The
|
||||
// handle will be copied by this method, so the original handle should still be
|
||||
// freed by the caller.
|
||||
//
|
||||
// This is effectively the inverse operation of [Handle.IntoRaw], and is used
|
||||
// for "deserialising" pathrs root handles.
|
||||
func HandleFromFile(file *os.File) (*Handle, error) {
|
||||
newFile, err := fdutils.DupFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("duplicate handle fd: %w", err)
|
||||
}
|
||||
return &Handle{inner: newFile}, nil
|
||||
}
|
||||
|
||||
// Open creates an "upgraded" file handle to the file referenced by the
|
||||
// [Handle]. Note that the original [Handle] is not consumed by this operation,
|
||||
// and can be opened multiple times.
|
||||
//
|
||||
// The handle returned is only usable for reading, and this is method is
|
||||
// shorthand for [Handle.OpenFile] with os.O_RDONLY.
|
||||
//
|
||||
// TODO: Rename these to "Reopen" or something.
|
||||
func (h *Handle) Open() (*os.File, error) {
|
||||
return h.OpenFile(os.O_RDONLY)
|
||||
}
|
||||
|
||||
// OpenFile creates an "upgraded" file handle to the file referenced by the
|
||||
// [Handle]. Note that the original [Handle] is not consumed by this operation,
|
||||
// and can be opened multiple times.
|
||||
//
|
||||
// The provided flags indicate which open(2) flags are used to create the new
|
||||
// handle.
|
||||
//
|
||||
// TODO: Rename these to "Reopen" or something.
|
||||
func (h *Handle) OpenFile(flags int) (*os.File, error) {
|
||||
return fdutils.WithFileFd(h.inner, func(fd uintptr) (*os.File, error) {
|
||||
newFd, err := libpathrs.Reopen(fd, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.NewFile(newFd, h.inner.Name()), nil
|
||||
})
|
||||
}
|
||||
|
||||
// IntoFile unwraps the [Handle] into its underlying [os.File].
|
||||
//
|
||||
// You almost certainly want to use [Handle.OpenFile] to get a non-O_PATH
|
||||
// version of this [Handle].
|
||||
//
|
||||
// This operation returns the internal [os.File] of the [Handle] directly, so
|
||||
// calling [Handle.Close] will also close any copies of the returned [os.File].
|
||||
// If you want to get an independent copy, use [Handle.Clone] followed by
|
||||
// [Handle.IntoFile] on the cloned [Handle].
|
||||
//
|
||||
// [os.File]: https://pkg.go.dev/os#File
|
||||
func (h *Handle) IntoFile() *os.File {
|
||||
// TODO: Figure out if we really don't want to make a copy.
|
||||
// TODO: We almost certainly want to clear r.inner here, but we can't do
|
||||
// that easily atomically (we could use atomic.Value but that'll make
|
||||
// things quite a bit uglier).
|
||||
return h.inner
|
||||
}
|
||||
|
||||
// Clone creates a copy of a [Handle], such that it has a separate lifetime to
|
||||
// the original (while referring to the same underlying file).
|
||||
func (h *Handle) Clone() (*Handle, error) {
|
||||
return HandleFromFile(h.inner)
|
||||
}
|
||||
|
||||
// Close frees all of the resources used by the [Handle].
|
||||
func (h *Handle) Close() error {
|
||||
return h.inner.Close()
|
||||
}
|
||||
75
vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go
generated
vendored
75
vendor/cyphar.com/go-pathrs/internal/fdutils/fd_linux.go
generated
vendored
@@ -1,75 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
/*
|
||||
* libpathrs: safe path resolution on Linux
|
||||
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
* Copyright (C) 2019-2025 SUSE LLC
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// Package fdutils contains a few helper methods when dealing with *os.File and
|
||||
// file descriptors.
|
||||
package fdutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"cyphar.com/go-pathrs/internal/libpathrs"
|
||||
)
|
||||
|
||||
// DupFd makes a duplicate of the given fd.
|
||||
func DupFd(fd uintptr, name string) (*os.File, error) {
|
||||
newFd, err := unix.FcntlInt(fd, unix.F_DUPFD_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fcntl(F_DUPFD_CLOEXEC): %w", err)
|
||||
}
|
||||
return os.NewFile(uintptr(newFd), name), nil
|
||||
}
|
||||
|
||||
// WithFileFd is a more ergonomic wrapper around file.SyscallConn().Control().
|
||||
func WithFileFd[T any](file *os.File, fn func(fd uintptr) (T, error)) (T, error) {
|
||||
conn, err := file.SyscallConn()
|
||||
if err != nil {
|
||||
return *new(T), err
|
||||
}
|
||||
var (
|
||||
ret T
|
||||
innerErr error
|
||||
)
|
||||
if err := conn.Control(func(fd uintptr) {
|
||||
ret, innerErr = fn(fd)
|
||||
}); err != nil {
|
||||
return *new(T), err
|
||||
}
|
||||
return ret, innerErr
|
||||
}
|
||||
|
||||
// DupFile makes a duplicate of the given file.
|
||||
func DupFile(file *os.File) (*os.File, error) {
|
||||
return WithFileFd(file, func(fd uintptr) (*os.File, error) {
|
||||
return DupFd(fd, file.Name())
|
||||
})
|
||||
}
|
||||
|
||||
// MkFile creates a new *os.File from the provided file descriptor. However,
|
||||
// unlike os.NewFile, the file's Name is based on the real path (provided by
|
||||
// /proc/self/fd/$n).
|
||||
func MkFile(fd uintptr) (*os.File, error) {
|
||||
fdPath := fmt.Sprintf("fd/%d", fd)
|
||||
fdName, err := libpathrs.ProcReadlinkat(libpathrs.ProcDefaultRootFd, libpathrs.ProcThreadSelf, fdPath)
|
||||
if err != nil {
|
||||
_ = unix.Close(int(fd))
|
||||
return nil, fmt.Errorf("failed to fetch real name of fd %d: %w", fd, err)
|
||||
}
|
||||
// TODO: Maybe we should prefix this name with something to indicate to
|
||||
// users that they must not use this path as a "safe" path. Something like
|
||||
// "//pathrs-handle:/foo/bar"?
|
||||
return os.NewFile(fd, fdName), nil
|
||||
}
|
||||
40
vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go
generated
vendored
40
vendor/cyphar.com/go-pathrs/internal/libpathrs/error_unix.go
generated
vendored
@@ -1,40 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
// TODO: Use "go:build unix" once we bump the minimum Go version 1.19.
|
||||
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
/*
|
||||
* libpathrs: safe path resolution on Linux
|
||||
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
* Copyright (C) 2019-2025 SUSE LLC
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package libpathrs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Error represents an underlying libpathrs error.
|
||||
type Error struct {
|
||||
description string
|
||||
errno syscall.Errno
|
||||
}
|
||||
|
||||
// Error returns a textual description of the error.
|
||||
func (err *Error) Error() string {
|
||||
return err.description
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying error which was wrapped by this error (if
|
||||
// applicable).
|
||||
func (err *Error) Unwrap() error {
|
||||
if err.errno != 0 {
|
||||
return err.errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
337
vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go
generated
vendored
337
vendor/cyphar.com/go-pathrs/internal/libpathrs/libpathrs_linux.go
generated
vendored
@@ -1,337 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
/*
|
||||
* libpathrs: safe path resolution on Linux
|
||||
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
* Copyright (C) 2019-2025 SUSE LLC
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// Package libpathrs is an internal thin wrapper around the libpathrs C API.
|
||||
package libpathrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
// TODO: Figure out if we need to add support for linking against libpathrs
|
||||
// statically even if in dynamically linked builds in order to make
|
||||
// packaging a bit easier (using "-Wl,-Bstatic -lpathrs -Wl,-Bdynamic" or
|
||||
// "-l:pathrs.a").
|
||||
#cgo pkg-config: pathrs
|
||||
#include <pathrs.h>
|
||||
|
||||
// This is a workaround for unsafe.Pointer() not working for non-void pointers.
|
||||
char *cast_ptr(void *ptr) { return ptr; }
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func fetchError(errID C.int) error {
|
||||
if errID >= C.__PATHRS_MAX_ERR_VALUE {
|
||||
return nil
|
||||
}
|
||||
cErr := C.pathrs_errorinfo(errID)
|
||||
defer C.pathrs_errorinfo_free(cErr)
|
||||
|
||||
var err error
|
||||
if cErr != nil {
|
||||
err = &Error{
|
||||
errno: syscall.Errno(cErr.saved_errno),
|
||||
description: C.GoString(cErr.description),
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// OpenRoot wraps pathrs_open_root.
|
||||
func OpenRoot(path string) (uintptr, error) {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
fd := C.pathrs_open_root(cPath)
|
||||
return uintptr(fd), fetchError(fd)
|
||||
}
|
||||
|
||||
// Reopen wraps pathrs_reopen.
|
||||
func Reopen(fd uintptr, flags int) (uintptr, error) {
|
||||
newFd := C.pathrs_reopen(C.int(fd), C.int(flags))
|
||||
return uintptr(newFd), fetchError(newFd)
|
||||
}
|
||||
|
||||
// InRootResolve wraps pathrs_inroot_resolve.
|
||||
func InRootResolve(rootFd uintptr, path string) (uintptr, error) {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
fd := C.pathrs_inroot_resolve(C.int(rootFd), cPath)
|
||||
return uintptr(fd), fetchError(fd)
|
||||
}
|
||||
|
||||
// InRootResolveNoFollow wraps pathrs_inroot_resolve_nofollow.
|
||||
func InRootResolveNoFollow(rootFd uintptr, path string) (uintptr, error) {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
fd := C.pathrs_inroot_resolve_nofollow(C.int(rootFd), cPath)
|
||||
return uintptr(fd), fetchError(fd)
|
||||
}
|
||||
|
||||
// InRootOpen wraps pathrs_inroot_open.
|
||||
func InRootOpen(rootFd uintptr, path string, flags int) (uintptr, error) {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
fd := C.pathrs_inroot_open(C.int(rootFd), cPath, C.int(flags))
|
||||
return uintptr(fd), fetchError(fd)
|
||||
}
|
||||
|
||||
// InRootReadlink wraps pathrs_inroot_readlink.
|
||||
func InRootReadlink(rootFd uintptr, path string) (string, error) {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
size := 128
|
||||
for {
|
||||
linkBuf := make([]byte, size)
|
||||
n := C.pathrs_inroot_readlink(C.int(rootFd), cPath, C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.ulong(len(linkBuf)))
|
||||
switch {
|
||||
case int(n) < C.__PATHRS_MAX_ERR_VALUE:
|
||||
return "", fetchError(n)
|
||||
case int(n) <= len(linkBuf):
|
||||
return string(linkBuf[:int(n)]), nil
|
||||
default:
|
||||
// The contents were truncated. Unlike readlinkat, pathrs returns
|
||||
// the size of the link when it checked. So use the returned size
|
||||
// as a basis for the reallocated size (but in order to avoid a DoS
|
||||
// where a magic-link is growing by a single byte each iteration,
|
||||
// make sure we are a fair bit larger).
|
||||
size += int(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InRootRmdir wraps pathrs_inroot_rmdir.
|
||||
func InRootRmdir(rootFd uintptr, path string) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
err := C.pathrs_inroot_rmdir(C.int(rootFd), cPath)
|
||||
return fetchError(err)
|
||||
}
|
||||
|
||||
// InRootUnlink wraps pathrs_inroot_unlink.
|
||||
func InRootUnlink(rootFd uintptr, path string) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
err := C.pathrs_inroot_unlink(C.int(rootFd), cPath)
|
||||
return fetchError(err)
|
||||
}
|
||||
|
||||
// InRootRemoveAll wraps pathrs_inroot_remove_all.
|
||||
func InRootRemoveAll(rootFd uintptr, path string) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
err := C.pathrs_inroot_remove_all(C.int(rootFd), cPath)
|
||||
return fetchError(err)
|
||||
}
|
||||
|
||||
// InRootCreat wraps pathrs_inroot_creat.
|
||||
func InRootCreat(rootFd uintptr, path string, flags int, mode uint32) (uintptr, error) {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
fd := C.pathrs_inroot_creat(C.int(rootFd), cPath, C.int(flags), C.uint(mode))
|
||||
return uintptr(fd), fetchError(fd)
|
||||
}
|
||||
|
||||
// InRootRename wraps pathrs_inroot_rename.
|
||||
func InRootRename(rootFd uintptr, src, dst string, flags uint) error {
|
||||
cSrc := C.CString(src)
|
||||
defer C.free(unsafe.Pointer(cSrc))
|
||||
|
||||
cDst := C.CString(dst)
|
||||
defer C.free(unsafe.Pointer(cDst))
|
||||
|
||||
err := C.pathrs_inroot_rename(C.int(rootFd), cSrc, cDst, C.uint(flags))
|
||||
return fetchError(err)
|
||||
}
|
||||
|
||||
// InRootMkdir wraps pathrs_inroot_mkdir.
|
||||
func InRootMkdir(rootFd uintptr, path string, mode uint32) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
err := C.pathrs_inroot_mkdir(C.int(rootFd), cPath, C.uint(mode))
|
||||
return fetchError(err)
|
||||
}
|
||||
|
||||
// InRootMkdirAll wraps pathrs_inroot_mkdir_all.
|
||||
func InRootMkdirAll(rootFd uintptr, path string, mode uint32) (uintptr, error) {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
fd := C.pathrs_inroot_mkdir_all(C.int(rootFd), cPath, C.uint(mode))
|
||||
return uintptr(fd), fetchError(fd)
|
||||
}
|
||||
|
||||
// InRootMknod wraps pathrs_inroot_mknod.
|
||||
func InRootMknod(rootFd uintptr, path string, mode uint32, dev uint64) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
err := C.pathrs_inroot_mknod(C.int(rootFd), cPath, C.uint(mode), C.dev_t(dev))
|
||||
return fetchError(err)
|
||||
}
|
||||
|
||||
// InRootSymlink wraps pathrs_inroot_symlink.
|
||||
func InRootSymlink(rootFd uintptr, path, target string) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
cTarget := C.CString(target)
|
||||
defer C.free(unsafe.Pointer(cTarget))
|
||||
|
||||
err := C.pathrs_inroot_symlink(C.int(rootFd), cPath, cTarget)
|
||||
return fetchError(err)
|
||||
}
|
||||
|
||||
// InRootHardlink wraps pathrs_inroot_hardlink.
|
||||
func InRootHardlink(rootFd uintptr, path, target string) error {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
cTarget := C.CString(target)
|
||||
defer C.free(unsafe.Pointer(cTarget))
|
||||
|
||||
err := C.pathrs_inroot_hardlink(C.int(rootFd), cPath, cTarget)
|
||||
return fetchError(err)
|
||||
}
|
||||
|
||||
// ProcBase is pathrs_proc_base_t (uint64_t).
|
||||
type ProcBase C.pathrs_proc_base_t
|
||||
|
||||
// FIXME: We need to open-code the constants because CGo unfortunately will
|
||||
// implicitly convert any non-literal constants (i.e. those resolved using gcc)
|
||||
// to signed integers. See <https://github.com/golang/go/issues/39136> for some
|
||||
// more information on the underlying issue (though.
|
||||
const (
|
||||
// ProcRoot is PATHRS_PROC_ROOT.
|
||||
ProcRoot ProcBase = 0xFFFF_FFFE_7072_6F63 // C.PATHRS_PROC_ROOT
|
||||
// ProcSelf is PATHRS_PROC_SELF.
|
||||
ProcSelf ProcBase = 0xFFFF_FFFE_091D_5E1F // C.PATHRS_PROC_SELF
|
||||
// ProcThreadSelf is PATHRS_PROC_THREAD_SELF.
|
||||
ProcThreadSelf ProcBase = 0xFFFF_FFFE_3EAD_5E1F // C.PATHRS_PROC_THREAD_SELF
|
||||
|
||||
// ProcBaseTypeMask is __PATHRS_PROC_TYPE_MASK.
|
||||
ProcBaseTypeMask ProcBase = 0xFFFF_FFFF_0000_0000 // C.__PATHRS_PROC_TYPE_MASK
|
||||
// ProcBaseTypePid is __PATHRS_PROC_TYPE_PID.
|
||||
ProcBaseTypePid ProcBase = 0x8000_0000_0000_0000 // C.__PATHRS_PROC_TYPE_PID
|
||||
|
||||
// ProcDefaultRootFd is PATHRS_PROC_DEFAULT_ROOTFD.
|
||||
ProcDefaultRootFd = -int(syscall.EBADF) // C.PATHRS_PROC_DEFAULT_ROOTFD
|
||||
)
|
||||
|
||||
func assertEqual[T comparable](a, b T, msg string) {
|
||||
if a != b {
|
||||
panic(fmt.Sprintf("%s ((%T) %#v != (%T) %#v)", msg, a, a, b, b))
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the values above match the actual C values. Unfortunately, Go
|
||||
// only allows us to forcefully cast int64 to uint64 if you use a temporary
|
||||
// variable, which means we cannot do it in a const context and thus need to do
|
||||
// it at runtime (even though it is a check that fundamentally could be done at
|
||||
// compile-time)...
|
||||
func init() {
|
||||
var (
|
||||
actualProcRoot int64 = C.PATHRS_PROC_ROOT
|
||||
actualProcSelf int64 = C.PATHRS_PROC_SELF
|
||||
actualProcThreadSelf int64 = C.PATHRS_PROC_THREAD_SELF
|
||||
)
|
||||
|
||||
assertEqual(ProcRoot, ProcBase(actualProcRoot), "PATHRS_PROC_ROOT")
|
||||
assertEqual(ProcSelf, ProcBase(actualProcSelf), "PATHRS_PROC_SELF")
|
||||
assertEqual(ProcThreadSelf, ProcBase(actualProcThreadSelf), "PATHRS_PROC_THREAD_SELF")
|
||||
|
||||
var (
|
||||
actualProcBaseTypeMask uint64 = C.__PATHRS_PROC_TYPE_MASK
|
||||
actualProcBaseTypePid uint64 = C.__PATHRS_PROC_TYPE_PID
|
||||
)
|
||||
|
||||
assertEqual(ProcBaseTypeMask, ProcBase(actualProcBaseTypeMask), "__PATHRS_PROC_TYPE_MASK")
|
||||
assertEqual(ProcBaseTypePid, ProcBase(actualProcBaseTypePid), "__PATHRS_PROC_TYPE_PID")
|
||||
|
||||
assertEqual(ProcDefaultRootFd, int(C.PATHRS_PROC_DEFAULT_ROOTFD), "PATHRS_PROC_DEFAULT_ROOTFD")
|
||||
}
|
||||
|
||||
// ProcPid reimplements the PROC_PID(x) conversion.
|
||||
func ProcPid(pid uint32) ProcBase { return ProcBaseTypePid | ProcBase(pid) }
|
||||
|
||||
// ProcOpenat wraps pathrs_proc_openat.
|
||||
func ProcOpenat(procRootFd int, base ProcBase, path string, flags int) (uintptr, error) {
|
||||
cBase := C.pathrs_proc_base_t(base)
|
||||
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
fd := C.pathrs_proc_openat(C.int(procRootFd), cBase, cPath, C.int(flags))
|
||||
return uintptr(fd), fetchError(fd)
|
||||
}
|
||||
|
||||
// ProcReadlinkat wraps pathrs_proc_readlinkat.
|
||||
func ProcReadlinkat(procRootFd int, base ProcBase, path string) (string, error) {
|
||||
// TODO: See if we can unify this code with InRootReadlink.
|
||||
|
||||
cBase := C.pathrs_proc_base_t(base)
|
||||
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
size := 128
|
||||
for {
|
||||
linkBuf := make([]byte, size)
|
||||
n := C.pathrs_proc_readlinkat(
|
||||
C.int(procRootFd), cBase, cPath,
|
||||
C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.ulong(len(linkBuf)))
|
||||
switch {
|
||||
case int(n) < C.__PATHRS_MAX_ERR_VALUE:
|
||||
return "", fetchError(n)
|
||||
case int(n) <= len(linkBuf):
|
||||
return string(linkBuf[:int(n)]), nil
|
||||
default:
|
||||
// The contents were truncated. Unlike readlinkat, pathrs returns
|
||||
// the size of the link when it checked. So use the returned size
|
||||
// as a basis for the reallocated size (but in order to avoid a DoS
|
||||
// where a magic-link is growing by a single byte each iteration,
|
||||
// make sure we are a fair bit larger).
|
||||
size += int(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProcfsOpenHow is pathrs_procfs_open_how (struct).
|
||||
type ProcfsOpenHow C.pathrs_procfs_open_how
|
||||
|
||||
const (
|
||||
// ProcfsNewUnmasked is PATHRS_PROCFS_NEW_UNMASKED.
|
||||
ProcfsNewUnmasked = C.PATHRS_PROCFS_NEW_UNMASKED
|
||||
)
|
||||
|
||||
// Flags returns a pointer to the internal flags field to allow other packages
|
||||
// to modify structure fields that are internal due to Go's visibility model.
|
||||
func (how *ProcfsOpenHow) Flags() *C.uint64_t { return &how.flags }
|
||||
|
||||
// ProcfsOpen is pathrs_procfs_open (sizeof(*how) is passed automatically).
|
||||
func ProcfsOpen(how *ProcfsOpenHow) (uintptr, error) {
|
||||
fd := C.pathrs_procfs_open((*C.pathrs_procfs_open_how)(how), C.size_t(unsafe.Sizeof(*how)))
|
||||
return uintptr(fd), fetchError(fd)
|
||||
}
|
||||
246
vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go
generated
vendored
246
vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go
generated
vendored
@@ -1,246 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
/*
|
||||
* libpathrs: safe path resolution on Linux
|
||||
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
* Copyright (C) 2019-2025 SUSE LLC
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// Package procfs provides a safe API for operating on /proc on Linux.
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"cyphar.com/go-pathrs/internal/fdutils"
|
||||
"cyphar.com/go-pathrs/internal/libpathrs"
|
||||
)
|
||||
|
||||
// ProcBase is used with [ProcReadlink] and related functions to indicate what
|
||||
// /proc subpath path operations should be done relative to.
|
||||
type ProcBase struct {
|
||||
inner libpathrs.ProcBase
|
||||
}
|
||||
|
||||
var (
|
||||
// ProcRoot indicates to use /proc. Note that this mode may be more
|
||||
// expensive because we have to take steps to try to avoid leaking unmasked
|
||||
// procfs handles, so you should use [ProcBaseSelf] if you can.
|
||||
ProcRoot = ProcBase{inner: libpathrs.ProcRoot}
|
||||
// ProcSelf indicates to use /proc/self. For most programs, this is the
|
||||
// standard choice.
|
||||
ProcSelf = ProcBase{inner: libpathrs.ProcSelf}
|
||||
// ProcThreadSelf indicates to use /proc/thread-self. In multi-threaded
|
||||
// programs where one thread has a different CLONE_FS, it is possible for
|
||||
// /proc/self to point the wrong thread and so /proc/thread-self may be
|
||||
// necessary.
|
||||
ProcThreadSelf = ProcBase{inner: libpathrs.ProcThreadSelf}
|
||||
)
|
||||
|
||||
// ProcPid returns a ProcBase which indicates to use /proc/$pid for the given
|
||||
// PID (or TID). Be aware that due to PID recycling, using this is generally
|
||||
// not safe except in certain circumstances. Namely:
|
||||
//
|
||||
// - PID 1 (the init process), as that PID cannot ever get recycled.
|
||||
// - Your current PID (though you should just use [ProcBaseSelf]).
|
||||
// - Your current TID if you have used [runtime.LockOSThread] (though you
|
||||
// should just use [ProcBaseThreadSelf]).
|
||||
// - PIDs of child processes (as long as you are sure that no other part of
|
||||
// your program incorrectly catches or ignores SIGCHLD, and that you do it
|
||||
// *before* you call wait(2)or any equivalent method that could reap
|
||||
// zombies).
|
||||
func ProcPid(pid int) ProcBase {
|
||||
if pid < 0 || pid >= 1<<31 {
|
||||
panic("invalid ProcBasePid value") // TODO: should this be an error?
|
||||
}
|
||||
return ProcBase{inner: libpathrs.ProcPid(uint32(pid))}
|
||||
}
|
||||
|
||||
// ThreadCloser is a callback that needs to be called when you are done
|
||||
// operating on an [os.File] fetched using [Handle.OpenThreadSelf].
|
||||
//
|
||||
// [os.File]: https://pkg.go.dev/os#File
|
||||
type ThreadCloser func()
|
||||
|
||||
// Handle is a wrapper around an *os.File handle to "/proc", which can be
|
||||
// used to do further procfs-related operations in a safe way.
|
||||
type Handle struct {
|
||||
inner *os.File
|
||||
}
|
||||
|
||||
// Close releases all internal resources for this [Handle].
|
||||
//
|
||||
// Note that if the handle is actually the global cached handle, this operation
|
||||
// is a no-op.
|
||||
func (proc *Handle) Close() error {
|
||||
var err error
|
||||
if proc.inner != nil {
|
||||
err = proc.inner.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// OpenOption is a configuration function passed as an argument to [Open].
|
||||
type OpenOption func(*libpathrs.ProcfsOpenHow) error
|
||||
|
||||
// UnmaskedProcRoot can be passed to [Open] to request an unmasked procfs
|
||||
// handle be created.
|
||||
//
|
||||
// procfs, err := procfs.OpenRoot(procfs.UnmaskedProcRoot)
|
||||
func UnmaskedProcRoot(how *libpathrs.ProcfsOpenHow) error {
|
||||
*how.Flags() |= libpathrs.ProcfsNewUnmasked
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open creates a new [Handle] to a safe "/proc", based on the passed
|
||||
// configuration options (in the form of a series of [OpenOption]s).
|
||||
func Open(opts ...OpenOption) (*Handle, error) {
|
||||
var how libpathrs.ProcfsOpenHow
|
||||
for _, opt := range opts {
|
||||
if err := opt(&how); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
fd, err := libpathrs.ProcfsOpen(&how)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var procFile *os.File
|
||||
if int(fd) >= 0 {
|
||||
procFile = os.NewFile(fd, "/proc")
|
||||
}
|
||||
// TODO: Check that fd == PATHRS_PROC_DEFAULT_ROOTFD in the <0 case?
|
||||
return &Handle{inner: procFile}, nil
|
||||
}
|
||||
|
||||
// TODO: Switch to something fdutils.WithFileFd-like.
|
||||
func (proc *Handle) fd() int {
|
||||
if proc.inner != nil {
|
||||
return int(proc.inner.Fd())
|
||||
}
|
||||
return libpathrs.ProcDefaultRootFd
|
||||
}
|
||||
|
||||
// TODO: Should we expose open?
|
||||
func (proc *Handle) open(base ProcBase, path string, flags int) (_ *os.File, Closer ThreadCloser, Err error) {
|
||||
var closer ThreadCloser
|
||||
if base == ProcThreadSelf {
|
||||
runtime.LockOSThread()
|
||||
closer = runtime.UnlockOSThread
|
||||
}
|
||||
defer func() {
|
||||
if closer != nil && Err != nil {
|
||||
closer()
|
||||
Closer = nil
|
||||
}
|
||||
}()
|
||||
|
||||
fd, err := libpathrs.ProcOpenat(proc.fd(), base.inner, path, flags)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
file, err := fdutils.MkFile(fd)
|
||||
return file, closer, err
|
||||
}
|
||||
|
||||
// OpenRoot safely opens a given path from inside /proc/.
|
||||
//
|
||||
// This function must only be used for accessing global information from procfs
|
||||
// (such as /proc/cpuinfo) or information about other processes (such as
|
||||
// /proc/1). Accessing your own process information should be done using
|
||||
// [Handle.OpenSelf] or [Handle.OpenThreadSelf].
|
||||
func (proc *Handle) OpenRoot(path string, flags int) (*os.File, error) {
|
||||
file, closer, err := proc.open(ProcRoot, path, flags)
|
||||
if closer != nil {
|
||||
// should not happen
|
||||
panic("non-zero closer returned from procOpen(ProcRoot)")
|
||||
}
|
||||
return file, err
|
||||
}
|
||||
|
||||
// OpenSelf safely opens a given path from inside /proc/self/.
|
||||
//
|
||||
// This method is recommend for getting process information about the current
|
||||
// process for almost all Go processes *except* for cases where there are
|
||||
// [runtime.LockOSThread] threads that have changed some aspect of their state
|
||||
// (such as through unshare(CLONE_FS) or changing namespaces).
|
||||
//
|
||||
// For such non-heterogeneous processes, /proc/self may reference to a task
|
||||
// that has different state from the current goroutine and so it may be
|
||||
// preferable to use [Handle.OpenThreadSelf]. The same is true if a user
|
||||
// really wants to inspect the current OS thread's information (such as
|
||||
// /proc/thread-self/stack or /proc/thread-self/status which is always uniquely
|
||||
// per-thread).
|
||||
//
|
||||
// Unlike [Handle.OpenThreadSelf], this method does not involve locking
|
||||
// the goroutine to the current OS thread and so is simpler to use and
|
||||
// theoretically has slightly less overhead.
|
||||
//
|
||||
// [runtime.LockOSThread]: https://pkg.go.dev/runtime#LockOSThread
|
||||
func (proc *Handle) OpenSelf(path string, flags int) (*os.File, error) {
|
||||
file, closer, err := proc.open(ProcSelf, path, flags)
|
||||
if closer != nil {
|
||||
// should not happen
|
||||
panic("non-zero closer returned from procOpen(ProcSelf)")
|
||||
}
|
||||
return file, err
|
||||
}
|
||||
|
||||
// OpenPid safely opens a given path from inside /proc/$pid/, where pid can be
|
||||
// either a PID or TID.
|
||||
//
|
||||
// This is effectively equivalent to calling [Handle.OpenRoot] with the
|
||||
// pid prefixed to the subpath.
|
||||
//
|
||||
// Be aware that due to PID recycling, using this is generally not safe except
|
||||
// in certain circumstances. See the documentation of [ProcPid] for more
|
||||
// details.
|
||||
func (proc *Handle) OpenPid(pid int, path string, flags int) (*os.File, error) {
|
||||
file, closer, err := proc.open(ProcPid(pid), path, flags)
|
||||
if closer != nil {
|
||||
// should not happen
|
||||
panic("non-zero closer returned from procOpen(ProcPidOpen)")
|
||||
}
|
||||
return file, err
|
||||
}
|
||||
|
||||
// OpenThreadSelf safely opens a given path from inside /proc/thread-self/.
|
||||
//
|
||||
// Most Go processes have heterogeneous threads (all threads have most of the
|
||||
// same kernel state such as CLONE_FS) and so [Handle.OpenSelf] is
|
||||
// preferable for most users.
|
||||
//
|
||||
// For non-heterogeneous threads, or users that actually want thread-specific
|
||||
// information (such as /proc/thread-self/stack or /proc/thread-self/status),
|
||||
// this method is necessary.
|
||||
//
|
||||
// Because Go can change the running OS thread of your goroutine without notice
|
||||
// (and then subsequently kill the old thread), this method will lock the
|
||||
// current goroutine to the OS thread (with [runtime.LockOSThread]) and the
|
||||
// caller is responsible for unlocking the the OS thread with the
|
||||
// [ThreadCloser] callback once they are done using the returned file. This
|
||||
// callback MUST be called AFTER you have finished using the returned
|
||||
// [os.File]. This callback is completely separate to [os.File.Close], so it
|
||||
// must be called regardless of how you close the handle.
|
||||
//
|
||||
// [runtime.LockOSThread]: https://pkg.go.dev/runtime#LockOSThread
|
||||
// [os.File]: https://pkg.go.dev/os#File
|
||||
// [os.File.Close]: https://pkg.go.dev/os#File.Close
|
||||
func (proc *Handle) OpenThreadSelf(path string, flags int) (*os.File, ThreadCloser, error) {
|
||||
return proc.open(ProcThreadSelf, path, flags)
|
||||
}
|
||||
|
||||
// Readlink safely reads the contents of a symlink from the given procfs base.
|
||||
//
|
||||
// This is effectively equivalent to doing an Open*(O_PATH|O_NOFOLLOW) of the
|
||||
// path and then doing unix.Readlinkat(fd, ""), but with the benefit that
|
||||
// thread locking is not necessary for [ProcThreadSelf].
|
||||
func (proc *Handle) Readlink(base ProcBase, path string) (string, error) {
|
||||
return libpathrs.ProcReadlinkat(proc.fd(), base.inner, path)
|
||||
}
|
||||
367
vendor/cyphar.com/go-pathrs/root_linux.go
generated
vendored
367
vendor/cyphar.com/go-pathrs/root_linux.go
generated
vendored
@@ -1,367 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
/*
|
||||
* libpathrs: safe path resolution on Linux
|
||||
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
* Copyright (C) 2019-2025 SUSE LLC
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package pathrs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"cyphar.com/go-pathrs/internal/fdutils"
|
||||
"cyphar.com/go-pathrs/internal/libpathrs"
|
||||
)
|
||||
|
||||
// Root is a handle to the root of a directory tree to resolve within. The only
|
||||
// purpose of this "root handle" is to perform operations within the directory
|
||||
// tree, or to get a [Handle] to inodes within the directory tree.
|
||||
//
|
||||
// At time of writing, it is considered a *VERY BAD IDEA* to open a [Root]
|
||||
// inside a possibly-attacker-controlled directory tree. While we do have
|
||||
// protections that should defend against it, it's far more dangerous than just
|
||||
// opening a directory tree which is not inside a potentially-untrusted
|
||||
// directory.
|
||||
type Root struct {
|
||||
inner *os.File
|
||||
}
|
||||
|
||||
// OpenRoot creates a new [Root] handle to the directory at the given path.
|
||||
func OpenRoot(path string) (*Root, error) {
|
||||
fd, err := libpathrs.OpenRoot(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file, err := fdutils.MkFile(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Root{inner: file}, nil
|
||||
}
|
||||
|
||||
// RootFromFile creates a new [Root] handle from an [os.File] referencing a
|
||||
// directory. The provided file will be duplicated, so the original file should
|
||||
// still be closed by the caller.
|
||||
//
|
||||
// This is effectively the inverse operation of [Root.IntoFile].
|
||||
//
|
||||
// [os.File]: https://pkg.go.dev/os#File
|
||||
func RootFromFile(file *os.File) (*Root, error) {
|
||||
newFile, err := fdutils.DupFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("duplicate root fd: %w", err)
|
||||
}
|
||||
return &Root{inner: newFile}, nil
|
||||
}
|
||||
|
||||
// Resolve resolves the given path within the [Root]'s directory tree, and
|
||||
// returns a [Handle] to the resolved path. The path must already exist,
|
||||
// otherwise an error will occur.
|
||||
//
|
||||
// All symlinks (including trailing symlinks) are followed, but they are
|
||||
// resolved within the rootfs. If you wish to open a handle to the symlink
|
||||
// itself, use [ResolveNoFollow].
|
||||
func (r *Root) Resolve(path string) (*Handle, error) {
|
||||
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
|
||||
handleFd, err := libpathrs.InRootResolve(rootFd, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handleFile, err := fdutils.MkFile(handleFd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Handle{inner: handleFile}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// ResolveNoFollow is effectively an O_NOFOLLOW version of [Resolve]. Their
|
||||
// behaviour is identical, except that *trailing* symlinks will not be
|
||||
// followed. If the final component is a trailing symlink, an O_PATH|O_NOFOLLOW
|
||||
// handle to the symlink itself is returned.
|
||||
func (r *Root) ResolveNoFollow(path string) (*Handle, error) {
|
||||
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
|
||||
handleFd, err := libpathrs.InRootResolveNoFollow(rootFd, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handleFile, err := fdutils.MkFile(handleFd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Handle{inner: handleFile}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Open is effectively shorthand for [Resolve] followed by [Handle.Open], but
|
||||
// can be slightly more efficient (it reduces CGo overhead and the number of
|
||||
// syscalls used when using the openat2-based resolver) and is arguably more
|
||||
// ergonomic to use.
|
||||
//
|
||||
// This is effectively equivalent to [os.Open].
|
||||
//
|
||||
// [os.Open]: https://pkg.go.dev/os#Open
|
||||
func (r *Root) Open(path string) (*os.File, error) {
|
||||
return r.OpenFile(path, os.O_RDONLY)
|
||||
}
|
||||
|
||||
// OpenFile is effectively shorthand for [Resolve] followed by
|
||||
// [Handle.OpenFile], but can be slightly more efficient (it reduces CGo
|
||||
// overhead and the number of syscalls used when using the openat2-based
|
||||
// resolver) and is arguably more ergonomic to use.
|
||||
//
|
||||
// However, if flags contains os.O_NOFOLLOW and the path is a symlink, then
|
||||
// OpenFile's behaviour will match that of openat2. In most cases an error will
|
||||
// be returned, but if os.O_PATH is provided along with os.O_NOFOLLOW then a
|
||||
// file equivalent to [ResolveNoFollow] will be returned instead.
|
||||
//
|
||||
// This is effectively equivalent to [os.OpenFile], except that os.O_CREAT is
|
||||
// not supported.
|
||||
//
|
||||
// [os.OpenFile]: https://pkg.go.dev/os#OpenFile
|
||||
func (r *Root) OpenFile(path string, flags int) (*os.File, error) {
|
||||
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*os.File, error) {
|
||||
fd, err := libpathrs.InRootOpen(rootFd, path, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fdutils.MkFile(fd)
|
||||
})
|
||||
}
|
||||
|
||||
// Create creates a file within the [Root]'s directory tree at the given path,
|
||||
// and returns a handle to the file. The provided mode is used for the new file
|
||||
// (the process's umask applies).
|
||||
//
|
||||
// Unlike [os.Create], if the file already exists an error is created rather
|
||||
// than the file being opened and truncated.
|
||||
//
|
||||
// [os.Create]: https://pkg.go.dev/os#Create
|
||||
func (r *Root) Create(path string, flags int, mode os.FileMode) (*os.File, error) {
|
||||
unixMode, err := toUnixMode(mode, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*os.File, error) {
|
||||
handleFd, err := libpathrs.InRootCreat(rootFd, path, flags, unixMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fdutils.MkFile(handleFd)
|
||||
})
|
||||
}
|
||||
|
||||
// Rename two paths within a [Root]'s directory tree. The flags argument is
|
||||
// identical to the RENAME_* flags to the renameat2(2) system call.
|
||||
func (r *Root) Rename(src, dst string, flags uint) error {
|
||||
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||
err := libpathrs.InRootRename(rootFd, src, dst, flags)
|
||||
return struct{}{}, err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveDir removes the named empty directory within a [Root]'s directory
|
||||
// tree.
|
||||
func (r *Root) RemoveDir(path string) error {
|
||||
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||
err := libpathrs.InRootRmdir(rootFd, path)
|
||||
return struct{}{}, err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveFile removes the named file within a [Root]'s directory tree.
|
||||
func (r *Root) RemoveFile(path string) error {
|
||||
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||
err := libpathrs.InRootUnlink(rootFd, path)
|
||||
return struct{}{}, err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove removes the named file or (empty) directory within a [Root]'s
|
||||
// directory tree.
|
||||
//
|
||||
// This is effectively equivalent to [os.Remove].
|
||||
//
|
||||
// [os.Remove]: https://pkg.go.dev/os#Remove
|
||||
func (r *Root) Remove(path string) error {
|
||||
// In order to match os.Remove's implementation we need to also do both
|
||||
// syscalls unconditionally and adjust the error based on whether
|
||||
// pathrs_inroot_rmdir() returned ENOTDIR.
|
||||
unlinkErr := r.RemoveFile(path)
|
||||
if unlinkErr == nil {
|
||||
return nil
|
||||
}
|
||||
rmdirErr := r.RemoveDir(path)
|
||||
if rmdirErr == nil {
|
||||
return nil
|
||||
}
|
||||
// Both failed, adjust the error in the same way that os.Remove does.
|
||||
err := rmdirErr
|
||||
if errors.Is(err, syscall.ENOTDIR) {
|
||||
err = unlinkErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveAll recursively deletes a path and all of its children.
|
||||
//
|
||||
// This is effectively equivalent to [os.RemoveAll].
|
||||
//
|
||||
// [os.RemoveAll]: https://pkg.go.dev/os#RemoveAll
|
||||
func (r *Root) RemoveAll(path string) error {
|
||||
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||
err := libpathrs.InRootRemoveAll(rootFd, path)
|
||||
return struct{}{}, err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Mkdir creates a directory within a [Root]'s directory tree. The provided
|
||||
// mode is used for the new directory (the process's umask applies).
|
||||
//
|
||||
// This is effectively equivalent to [os.Mkdir].
|
||||
//
|
||||
// [os.Mkdir]: https://pkg.go.dev/os#Mkdir
|
||||
func (r *Root) Mkdir(path string, mode os.FileMode) error {
|
||||
unixMode, err := toUnixMode(mode, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||
err := libpathrs.InRootMkdir(rootFd, path, unixMode)
|
||||
return struct{}{}, err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// MkdirAll creates a directory (and any parent path components if they don't
|
||||
// exist) within a [Root]'s directory tree. The provided mode is used for any
|
||||
// directories created by this function (the process's umask applies).
|
||||
//
|
||||
// This is effectively equivalent to [os.MkdirAll].
|
||||
//
|
||||
// [os.MkdirAll]: https://pkg.go.dev/os#MkdirAll
|
||||
func (r *Root) MkdirAll(path string, mode os.FileMode) (*Handle, error) {
|
||||
unixMode, err := toUnixMode(mode, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
|
||||
handleFd, err := libpathrs.InRootMkdirAll(rootFd, path, unixMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handleFile, err := fdutils.MkFile(handleFd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Handle{inner: handleFile}, err
|
||||
})
|
||||
}
|
||||
|
||||
// Mknod creates a new device inode of the given type within a [Root]'s
|
||||
// directory tree. The provided mode is used for the new directory (the
|
||||
// process's umask applies).
|
||||
//
|
||||
// This is effectively equivalent to [unix.Mknod].
|
||||
//
|
||||
// [unix.Mknod]: https://pkg.go.dev/golang.org/x/sys/unix#Mknod
|
||||
func (r *Root) Mknod(path string, mode os.FileMode, dev uint64) error {
|
||||
unixMode, err := toUnixMode(mode, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||
err := libpathrs.InRootMknod(rootFd, path, unixMode, dev)
|
||||
return struct{}{}, err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Symlink creates a symlink within a [Root]'s directory tree. The symlink is
|
||||
// created at path and is a link to target.
|
||||
//
|
||||
// This is effectively equivalent to [os.Symlink].
|
||||
//
|
||||
// [os.Symlink]: https://pkg.go.dev/os#Symlink
|
||||
func (r *Root) Symlink(path, target string) error {
|
||||
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||
err := libpathrs.InRootSymlink(rootFd, path, target)
|
||||
return struct{}{}, err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Hardlink creates a hardlink within a [Root]'s directory tree. The hardlink
|
||||
// is created at path and is a link to target. Both paths are within the
|
||||
// [Root]'s directory tree (you cannot hardlink to a different [Root] or the
|
||||
// host).
|
||||
//
|
||||
// This is effectively equivalent to [os.Link].
|
||||
//
|
||||
// [os.Link]: https://pkg.go.dev/os#Link
|
||||
func (r *Root) Hardlink(path, target string) error {
|
||||
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
|
||||
err := libpathrs.InRootHardlink(rootFd, path, target)
|
||||
return struct{}{}, err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Readlink returns the target of a symlink with a [Root]'s directory tree.
|
||||
//
|
||||
// This is effectively equivalent to [os.Readlink].
|
||||
//
|
||||
// [os.Readlink]: https://pkg.go.dev/os#Readlink
|
||||
func (r *Root) Readlink(path string) (string, error) {
|
||||
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (string, error) {
|
||||
return libpathrs.InRootReadlink(rootFd, path)
|
||||
})
|
||||
}
|
||||
|
||||
// IntoFile unwraps the [Root] into its underlying [os.File].
|
||||
//
|
||||
// It is critical that you do not operate on this file descriptor yourself,
|
||||
// because the security properties of libpathrs depend on users doing all
|
||||
// relevant filesystem operations through libpathrs.
|
||||
//
|
||||
// This operation returns the internal [os.File] of the [Root] directly, so
|
||||
// calling [Root.Close] will also close any copies of the returned [os.File].
|
||||
// If you want to get an independent copy, use [Root.Clone] followed by
|
||||
// [Root.IntoFile] on the cloned [Root].
|
||||
//
|
||||
// [os.File]: https://pkg.go.dev/os#File
|
||||
func (r *Root) IntoFile() *os.File {
|
||||
// TODO: Figure out if we really don't want to make a copy.
|
||||
// TODO: We almost certainly want to clear r.inner here, but we can't do
|
||||
// that easily atomically (we could use atomic.Value but that'll make
|
||||
// things quite a bit uglier).
|
||||
return r.inner
|
||||
}
|
||||
|
||||
// Clone creates a copy of a [Root] handle, such that it has a separate
|
||||
// lifetime to the original (while referring to the same underlying directory).
|
||||
func (r *Root) Clone() (*Root, error) {
|
||||
return RootFromFile(r.inner)
|
||||
}
|
||||
|
||||
// Close frees all of the resources used by the [Root] handle.
|
||||
func (r *Root) Close() error {
|
||||
return r.inner.Close()
|
||||
}
|
||||
56
vendor/cyphar.com/go-pathrs/utils_linux.go
generated
vendored
56
vendor/cyphar.com/go-pathrs/utils_linux.go
generated
vendored
@@ -1,56 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
/*
|
||||
* libpathrs: safe path resolution on Linux
|
||||
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
* Copyright (C) 2019-2025 SUSE LLC
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package pathrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
//nolint:cyclop // this function needs to handle a lot of cases
|
||||
func toUnixMode(mode os.FileMode, needsType bool) (uint32, error) {
|
||||
sysMode := uint32(mode.Perm())
|
||||
switch mode & os.ModeType { //nolint:exhaustive // we only care about ModeType bits
|
||||
case 0:
|
||||
if needsType {
|
||||
sysMode |= unix.S_IFREG
|
||||
}
|
||||
case os.ModeDir:
|
||||
sysMode |= unix.S_IFDIR
|
||||
case os.ModeSymlink:
|
||||
sysMode |= unix.S_IFLNK
|
||||
case os.ModeCharDevice | os.ModeDevice:
|
||||
sysMode |= unix.S_IFCHR
|
||||
case os.ModeDevice:
|
||||
sysMode |= unix.S_IFBLK
|
||||
case os.ModeNamedPipe:
|
||||
sysMode |= unix.S_IFIFO
|
||||
case os.ModeSocket:
|
||||
sysMode |= unix.S_IFSOCK
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid mode filetype %+o", mode)
|
||||
}
|
||||
if mode&os.ModeSetuid != 0 {
|
||||
sysMode |= unix.S_ISUID
|
||||
}
|
||||
if mode&os.ModeSetgid != 0 {
|
||||
sysMode |= unix.S_ISGID
|
||||
}
|
||||
if mode&os.ModeSticky != 0 {
|
||||
sysMode |= unix.S_ISVTX
|
||||
}
|
||||
return sysMode, nil
|
||||
}
|
||||
2
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
2
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
@@ -1,7 +1,7 @@
|
||||
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
|
||||
reflection interface similar to Go's standard library `json` and `xml` packages.
|
||||
|
||||
Compatible with TOML version [v1.1.0](https://toml.io/en/v1.1.0).
|
||||
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
|
||||
|
||||
Documentation: https://pkg.go.dev/github.com/BurntSushi/toml
|
||||
|
||||
|
||||
9
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
9
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
@@ -206,13 +206,6 @@ func markDecodedRecursive(md *MetaData, tmap map[string]any) {
|
||||
markDecodedRecursive(md, tmap)
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
}
|
||||
if tarr, ok := tmap[key].([]map[string]any); ok {
|
||||
for _, elm := range tarr {
|
||||
md.context = append(md.context, key)
|
||||
markDecodedRecursive(md, elm)
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,7 +423,7 @@ func (md *MetaData) unifyString(data any, rv reflect.Value) error {
|
||||
if i, ok := data.(int64); ok {
|
||||
rv.SetString(strconv.FormatInt(i, 10))
|
||||
} else if f, ok := data.(float64); ok {
|
||||
rv.SetString(strconv.FormatFloat(f, 'g', -1, 64))
|
||||
rv.SetString(strconv.FormatFloat(f, 'f', -1, 64))
|
||||
} else {
|
||||
return md.badtype("string", data)
|
||||
}
|
||||
|
||||
79
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
79
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
@@ -228,9 +228,9 @@ func (enc *Encoder) eElement(rv reflect.Value) {
|
||||
}
|
||||
switch v.Location() {
|
||||
default:
|
||||
enc.write(v.Format(format))
|
||||
enc.wf(v.Format(format))
|
||||
case internal.LocalDatetime, internal.LocalDate, internal.LocalTime:
|
||||
enc.write(v.In(time.UTC).Format(format))
|
||||
enc.wf(v.In(time.UTC).Format(format))
|
||||
}
|
||||
return
|
||||
case Marshaler:
|
||||
@@ -279,40 +279,40 @@ func (enc *Encoder) eElement(rv reflect.Value) {
|
||||
case reflect.String:
|
||||
enc.writeQuoted(rv.String())
|
||||
case reflect.Bool:
|
||||
enc.write(strconv.FormatBool(rv.Bool()))
|
||||
enc.wf(strconv.FormatBool(rv.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
enc.write(strconv.FormatInt(rv.Int(), 10))
|
||||
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
enc.write(strconv.FormatUint(rv.Uint(), 10))
|
||||
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
||||
case reflect.Float32:
|
||||
f := rv.Float()
|
||||
if math.IsNaN(f) {
|
||||
if math.Signbit(f) {
|
||||
enc.write("-")
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.write("nan")
|
||||
enc.wf("nan")
|
||||
} else if math.IsInf(f, 0) {
|
||||
if math.Signbit(f) {
|
||||
enc.write("-")
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.write("inf")
|
||||
enc.wf("inf")
|
||||
} else {
|
||||
enc.write(floatAddDecimal(strconv.FormatFloat(f, 'g', -1, 32)))
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32)))
|
||||
}
|
||||
case reflect.Float64:
|
||||
f := rv.Float()
|
||||
if math.IsNaN(f) {
|
||||
if math.Signbit(f) {
|
||||
enc.write("-")
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.write("nan")
|
||||
enc.wf("nan")
|
||||
} else if math.IsInf(f, 0) {
|
||||
if math.Signbit(f) {
|
||||
enc.write("-")
|
||||
enc.wf("-")
|
||||
}
|
||||
enc.write("inf")
|
||||
enc.wf("inf")
|
||||
} else {
|
||||
enc.write(floatAddDecimal(strconv.FormatFloat(f, 'g', -1, 64)))
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64)))
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
enc.eArrayOrSliceElement(rv)
|
||||
@@ -330,32 +330,27 @@ func (enc *Encoder) eElement(rv reflect.Value) {
|
||||
// By the TOML spec, all floats must have a decimal with at least one number on
|
||||
// either side.
|
||||
func floatAddDecimal(fstr string) string {
|
||||
for _, c := range fstr {
|
||||
if c == 'e' { // Exponent syntax
|
||||
return fstr
|
||||
}
|
||||
if c == '.' {
|
||||
return fstr
|
||||
}
|
||||
if !strings.Contains(fstr, ".") {
|
||||
return fstr + ".0"
|
||||
}
|
||||
return fstr + ".0"
|
||||
return fstr
|
||||
}
|
||||
|
||||
func (enc *Encoder) writeQuoted(s string) {
|
||||
enc.write(`"` + dblQuotedReplacer.Replace(s) + `"`)
|
||||
enc.wf("\"%s\"", dblQuotedReplacer.Replace(s))
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
||||
length := rv.Len()
|
||||
enc.write("[")
|
||||
enc.wf("[")
|
||||
for i := 0; i < length; i++ {
|
||||
elem := eindirect(rv.Index(i))
|
||||
enc.eElement(elem)
|
||||
if i != length-1 {
|
||||
enc.write(", ")
|
||||
enc.wf(", ")
|
||||
}
|
||||
}
|
||||
enc.write("]")
|
||||
enc.wf("]")
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
||||
@@ -368,7 +363,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
||||
continue
|
||||
}
|
||||
enc.newline()
|
||||
enc.writef("%s[[%s]]", enc.indentStr(key), key)
|
||||
enc.wf("%s[[%s]]", enc.indentStr(key), key)
|
||||
enc.newline()
|
||||
enc.eMapOrStruct(key, trv, false)
|
||||
}
|
||||
@@ -381,7 +376,7 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
||||
enc.newline()
|
||||
}
|
||||
if len(key) > 0 {
|
||||
enc.writef("%s[%s]", enc.indentStr(key), key)
|
||||
enc.wf("%s[%s]", enc.indentStr(key), key)
|
||||
enc.newline()
|
||||
}
|
||||
enc.eMapOrStruct(key, rv, false)
|
||||
@@ -427,7 +422,7 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
|
||||
if inline {
|
||||
enc.writeKeyValue(Key{mapKey.String()}, val, true)
|
||||
if trailC || i != len(mapKeys)-1 {
|
||||
enc.write(", ")
|
||||
enc.wf(", ")
|
||||
}
|
||||
} else {
|
||||
enc.encode(key.add(mapKey.String()), val)
|
||||
@@ -436,12 +431,12 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
|
||||
}
|
||||
|
||||
if inline {
|
||||
enc.write("{")
|
||||
enc.wf("{")
|
||||
}
|
||||
writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0)
|
||||
writeMapKeys(mapKeysSub, false)
|
||||
if inline {
|
||||
enc.write("}")
|
||||
enc.wf("}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,7 +534,7 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
|
||||
if inline {
|
||||
enc.writeKeyValue(Key{keyName}, fieldVal, true)
|
||||
if fieldIndex[0] != totalFields-1 {
|
||||
enc.write(", ")
|
||||
enc.wf(", ")
|
||||
}
|
||||
} else {
|
||||
enc.encode(key.add(keyName), fieldVal)
|
||||
@@ -548,14 +543,14 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
|
||||
}
|
||||
|
||||
if inline {
|
||||
enc.write("{")
|
||||
enc.wf("{")
|
||||
}
|
||||
|
||||
l := len(fieldsDirect) + len(fieldsSub)
|
||||
writeFields(fieldsDirect, l)
|
||||
writeFields(fieldsSub, l)
|
||||
if inline {
|
||||
enc.write("}")
|
||||
enc.wf("}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,7 +700,7 @@ func isEmpty(rv reflect.Value) bool {
|
||||
|
||||
func (enc *Encoder) newline() {
|
||||
if enc.hasWritten {
|
||||
enc.write("\n")
|
||||
enc.wf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -727,22 +722,14 @@ func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
|
||||
enc.eElement(val)
|
||||
return
|
||||
}
|
||||
enc.writef("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
||||
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
||||
enc.eElement(val)
|
||||
if !inline {
|
||||
enc.newline()
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) write(s string) {
|
||||
_, err := enc.w.WriteString(s)
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
enc.hasWritten = true
|
||||
}
|
||||
|
||||
func (enc *Encoder) writef(format string, v ...any) {
|
||||
func (enc *Encoder) wf(format string, v ...any) {
|
||||
_, err := fmt.Fprintf(enc.w, format, v...)
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
|
||||
122
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
122
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
@@ -13,6 +13,7 @@ type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota
|
||||
itemNIL // used in the parser to indicate no type
|
||||
itemEOF
|
||||
itemText
|
||||
itemString
|
||||
@@ -46,13 +47,14 @@ func (p Position) String() string {
|
||||
}
|
||||
|
||||
type lexer struct {
|
||||
input string
|
||||
start int
|
||||
pos int
|
||||
line int
|
||||
state stateFn
|
||||
items chan item
|
||||
esc bool
|
||||
input string
|
||||
start int
|
||||
pos int
|
||||
line int
|
||||
state stateFn
|
||||
items chan item
|
||||
tomlNext bool
|
||||
esc bool
|
||||
|
||||
// Allow for backing up up to 4 runes. This is necessary because TOML
|
||||
// contains 3-rune tokens (""" and ''').
|
||||
@@ -88,13 +90,14 @@ func (lx *lexer) nextItem() item {
|
||||
}
|
||||
}
|
||||
|
||||
func lex(input string) *lexer {
|
||||
func lex(input string, tomlNext bool) *lexer {
|
||||
lx := &lexer{
|
||||
input: input,
|
||||
state: lexTop,
|
||||
items: make(chan item, 10),
|
||||
stack: make([]stateFn, 0, 10),
|
||||
line: 1,
|
||||
input: input,
|
||||
state: lexTop,
|
||||
items: make(chan item, 10),
|
||||
stack: make([]stateFn, 0, 10),
|
||||
line: 1,
|
||||
tomlNext: tomlNext,
|
||||
}
|
||||
return lx
|
||||
}
|
||||
@@ -105,7 +108,7 @@ func (lx *lexer) push(state stateFn) {
|
||||
|
||||
func (lx *lexer) pop() stateFn {
|
||||
if len(lx.stack) == 0 {
|
||||
panic("BUG in lexer: no states to pop")
|
||||
return lx.errorf("BUG in lexer: no states to pop")
|
||||
}
|
||||
last := lx.stack[len(lx.stack)-1]
|
||||
lx.stack = lx.stack[0 : len(lx.stack)-1]
|
||||
@@ -302,8 +305,6 @@ func lexTop(lx *lexer) stateFn {
|
||||
return lexTableStart
|
||||
case eof:
|
||||
if lx.pos > lx.start {
|
||||
// TODO: never reached? I think this can only occur on a bug in the
|
||||
// lexer(?)
|
||||
return lx.errorf("unexpected EOF")
|
||||
}
|
||||
lx.emit(itemEOF)
|
||||
@@ -391,6 +392,8 @@ func lexTableNameStart(lx *lexer) stateFn {
|
||||
func lexTableNameEnd(lx *lexer) stateFn {
|
||||
lx.skip(isWhitespace)
|
||||
switch r := lx.next(); {
|
||||
case isWhitespace(r):
|
||||
return lexTableNameEnd
|
||||
case r == '.':
|
||||
lx.ignore()
|
||||
return lexTableNameStart
|
||||
@@ -409,7 +412,7 @@ func lexTableNameEnd(lx *lexer) stateFn {
|
||||
// Lexes only one part, e.g. only 'a' inside 'a.b'.
|
||||
func lexBareName(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isBareKeyChar(r) {
|
||||
if isBareKeyChar(r, lx.tomlNext) {
|
||||
return lexBareName
|
||||
}
|
||||
lx.backup()
|
||||
@@ -417,23 +420,23 @@ func lexBareName(lx *lexer) stateFn {
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexQuotedName lexes one part of a quoted key or table name. It assumes that
|
||||
// it starts lexing at the quote itself (" or ').
|
||||
// lexBareName lexes one part of a key or table.
|
||||
//
|
||||
// It assumes that at least one valid character for the table has already been
|
||||
// read.
|
||||
//
|
||||
// Lexes only one part, e.g. only '"a"' inside '"a".b'.
|
||||
func lexQuotedName(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexValue)
|
||||
case r == '"':
|
||||
lx.ignore() // ignore the '"'
|
||||
return lexString
|
||||
case r == '\'':
|
||||
lx.ignore() // ignore the "'"
|
||||
return lexRawString
|
||||
|
||||
// TODO: I don't think any of the below conditions can ever be reached?
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexValue)
|
||||
case r == eof:
|
||||
return lx.errorf("unexpected EOF; expected value")
|
||||
default:
|
||||
@@ -461,19 +464,17 @@ func lexKeyStart(lx *lexer) stateFn {
|
||||
func lexKeyNameStart(lx *lexer) stateFn {
|
||||
lx.skip(isWhitespace)
|
||||
switch r := lx.peek(); {
|
||||
default:
|
||||
lx.push(lexKeyEnd)
|
||||
return lexBareName
|
||||
case r == '"' || r == '\'':
|
||||
lx.ignore()
|
||||
lx.push(lexKeyEnd)
|
||||
return lexQuotedName
|
||||
|
||||
// TODO: I think these can never be reached?
|
||||
case r == '=' || r == eof:
|
||||
return lx.errorf("unexpected '='")
|
||||
case r == '.':
|
||||
return lx.errorf("unexpected '.'")
|
||||
case r == '"' || r == '\'':
|
||||
lx.ignore()
|
||||
lx.push(lexKeyEnd)
|
||||
return lexQuotedName
|
||||
default:
|
||||
lx.push(lexKeyEnd)
|
||||
return lexBareName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +485,7 @@ func lexKeyEnd(lx *lexer) stateFn {
|
||||
switch r := lx.next(); {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexKeyEnd)
|
||||
case r == eof: // TODO: never reached
|
||||
case r == eof:
|
||||
return lx.errorf("unexpected EOF; expected key separator '='")
|
||||
case r == '.':
|
||||
lx.ignore()
|
||||
@@ -627,7 +628,10 @@ func lexInlineTableValue(lx *lexer) stateFn {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexInlineTableValue)
|
||||
case isNL(r):
|
||||
return lexSkip(lx, lexInlineTableValue)
|
||||
if lx.tomlNext {
|
||||
return lexSkip(lx, lexInlineTableValue)
|
||||
}
|
||||
return lx.errorPrevLine(errLexInlineTableNL{})
|
||||
case r == '#':
|
||||
lx.push(lexInlineTableValue)
|
||||
return lexCommentStart
|
||||
@@ -649,7 +653,10 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexInlineTableValueEnd)
|
||||
case isNL(r):
|
||||
return lexSkip(lx, lexInlineTableValueEnd)
|
||||
if lx.tomlNext {
|
||||
return lexSkip(lx, lexInlineTableValueEnd)
|
||||
}
|
||||
return lx.errorPrevLine(errLexInlineTableNL{})
|
||||
case r == '#':
|
||||
lx.push(lexInlineTableValueEnd)
|
||||
return lexCommentStart
|
||||
@@ -657,7 +664,10 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
lx.skip(isWhitespace)
|
||||
if lx.peek() == '}' {
|
||||
return lexInlineTableValueEnd
|
||||
if lx.tomlNext {
|
||||
return lexInlineTableValueEnd
|
||||
}
|
||||
return lx.errorf("trailing comma not allowed in inline tables")
|
||||
}
|
||||
return lexInlineTableValue
|
||||
case r == '}':
|
||||
@@ -845,6 +855,9 @@ func lexStringEscape(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch r {
|
||||
case 'e':
|
||||
if !lx.tomlNext {
|
||||
return lx.error(errLexEscape{r})
|
||||
}
|
||||
fallthrough
|
||||
case 'b':
|
||||
fallthrough
|
||||
@@ -865,6 +878,9 @@ func lexStringEscape(lx *lexer) stateFn {
|
||||
case '\\':
|
||||
return lx.pop()
|
||||
case 'x':
|
||||
if !lx.tomlNext {
|
||||
return lx.error(errLexEscape{r})
|
||||
}
|
||||
return lexHexEscape
|
||||
case 'u':
|
||||
return lexShortUnicodeEscape
|
||||
@@ -912,9 +928,19 @@ func lexLongUnicodeEscape(lx *lexer) stateFn {
|
||||
// lexBaseNumberOrDate can differentiate base prefixed integers from other
|
||||
// types.
|
||||
func lexNumberOrDateStart(lx *lexer) stateFn {
|
||||
if lx.next() == '0' {
|
||||
r := lx.next()
|
||||
switch r {
|
||||
case '0':
|
||||
return lexBaseNumberOrDate
|
||||
}
|
||||
|
||||
if !isDigit(r) {
|
||||
// The only way to reach this state is if the value starts
|
||||
// with a digit, so specifically treat anything else as an
|
||||
// error.
|
||||
return lx.errorf("expected a digit but got %q", r)
|
||||
}
|
||||
|
||||
return lexNumberOrDate
|
||||
}
|
||||
|
||||
@@ -1170,13 +1196,13 @@ func lexSkip(lx *lexer, nextState stateFn) stateFn {
|
||||
}
|
||||
|
||||
func (s stateFn) String() string {
|
||||
if s == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name()
|
||||
if i := strings.LastIndexByte(name, '.'); i > -1 {
|
||||
name = name[i+1:]
|
||||
}
|
||||
if s == nil {
|
||||
name = "<nil>"
|
||||
}
|
||||
return name + "()"
|
||||
}
|
||||
|
||||
@@ -1184,6 +1210,8 @@ func (itype itemType) String() string {
|
||||
switch itype {
|
||||
case itemError:
|
||||
return "Error"
|
||||
case itemNIL:
|
||||
return "NIL"
|
||||
case itemEOF:
|
||||
return "EOF"
|
||||
case itemText:
|
||||
@@ -1198,22 +1226,18 @@ func (itype itemType) String() string {
|
||||
return "Float"
|
||||
case itemDatetime:
|
||||
return "DateTime"
|
||||
case itemArray:
|
||||
return "Array"
|
||||
case itemArrayEnd:
|
||||
return "ArrayEnd"
|
||||
case itemTableStart:
|
||||
return "TableStart"
|
||||
case itemTableEnd:
|
||||
return "TableEnd"
|
||||
case itemArrayTableStart:
|
||||
return "ArrayTableStart"
|
||||
case itemArrayTableEnd:
|
||||
return "ArrayTableEnd"
|
||||
case itemKeyStart:
|
||||
return "KeyStart"
|
||||
case itemKeyEnd:
|
||||
return "KeyEnd"
|
||||
case itemArray:
|
||||
return "Array"
|
||||
case itemArrayEnd:
|
||||
return "ArrayEnd"
|
||||
case itemCommentStart:
|
||||
return "CommentStart"
|
||||
case itemInlineTableStart:
|
||||
@@ -1242,7 +1266,7 @@ func isDigit(r rune) bool { return r >= '0' && r <= '9' }
|
||||
func isBinary(r rune) bool { return r == '0' || r == '1' }
|
||||
func isOctal(r rune) bool { return r >= '0' && r <= '7' }
|
||||
func isHex(r rune) bool { return (r >= '0' && r <= '9') || (r|0x20 >= 'a' && r|0x20 <= 'f') }
|
||||
func isBareKeyChar(r rune) bool {
|
||||
func isBareKeyChar(r rune, tomlNext bool) bool {
|
||||
return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') ||
|
||||
(r >= '0' && r <= '9') || r == '_' || r == '-'
|
||||
}
|
||||
|
||||
46
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
46
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
@@ -3,6 +3,7 @@ package toml
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -16,6 +17,7 @@ type parser struct {
|
||||
context Key // Full key for the current hash in scope.
|
||||
currentKey string // Base key name for everything except hashes.
|
||||
pos Position // Current position in the TOML file.
|
||||
tomlNext bool
|
||||
|
||||
ordered []Key // List of keys in the order that they appear in the TOML data.
|
||||
|
||||
@@ -30,6 +32,8 @@ type keyInfo struct {
|
||||
}
|
||||
|
||||
func parse(data string) (p *parser, err error) {
|
||||
_, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110")
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if pErr, ok := r.(ParseError); ok {
|
||||
@@ -69,9 +73,10 @@ func parse(data string) (p *parser, err error) {
|
||||
p = &parser{
|
||||
keyInfo: make(map[string]keyInfo),
|
||||
mapping: make(map[string]any),
|
||||
lx: lex(data),
|
||||
lx: lex(data, tomlNext),
|
||||
ordered: make([]Key, 0),
|
||||
implicits: make(map[string]struct{}),
|
||||
tomlNext: tomlNext,
|
||||
}
|
||||
for {
|
||||
item := p.next()
|
||||
@@ -345,14 +350,17 @@ func (p *parser) valueFloat(it item) (any, tomlType) {
|
||||
var dtTypes = []struct {
|
||||
fmt string
|
||||
zone *time.Location
|
||||
next bool
|
||||
}{
|
||||
{time.RFC3339Nano, time.Local},
|
||||
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime},
|
||||
{"2006-01-02", internal.LocalDate},
|
||||
{"15:04:05.999999999", internal.LocalTime},
|
||||
{"2006-01-02T15:04Z07:00", time.Local},
|
||||
{"2006-01-02T15:04", internal.LocalDatetime},
|
||||
{"15:04", internal.LocalTime},
|
||||
{time.RFC3339Nano, time.Local, false},
|
||||
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime, false},
|
||||
{"2006-01-02", internal.LocalDate, false},
|
||||
{"15:04:05.999999999", internal.LocalTime, false},
|
||||
|
||||
// tomlNext
|
||||
{"2006-01-02T15:04Z07:00", time.Local, true},
|
||||
{"2006-01-02T15:04", internal.LocalDatetime, true},
|
||||
{"15:04", internal.LocalTime, true},
|
||||
}
|
||||
|
||||
func (p *parser) valueDatetime(it item) (any, tomlType) {
|
||||
@@ -363,6 +371,9 @@ func (p *parser) valueDatetime(it item) (any, tomlType) {
|
||||
err error
|
||||
)
|
||||
for _, dt := range dtTypes {
|
||||
if dt.next && !p.tomlNext {
|
||||
continue
|
||||
}
|
||||
t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone)
|
||||
if err == nil {
|
||||
if missingLeadingZero(it.val, dt.fmt) {
|
||||
@@ -633,11 +644,6 @@ func (p *parser) setValue(key string, value any) {
|
||||
// Note that since it has already been defined (as a hash), we don't
|
||||
// want to overwrite it. So our business is done.
|
||||
if p.isArray(keyContext) {
|
||||
if !p.isImplicit(keyContext) {
|
||||
if _, ok := hash[key]; ok {
|
||||
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||
}
|
||||
}
|
||||
p.removeImplicit(keyContext)
|
||||
hash[key] = value
|
||||
return
|
||||
@@ -796,8 +802,10 @@ func (p *parser) replaceEscapes(it item, str string) string {
|
||||
b.WriteByte(0x0d)
|
||||
skip = 1
|
||||
case 'e':
|
||||
b.WriteByte(0x1b)
|
||||
skip = 1
|
||||
if p.tomlNext {
|
||||
b.WriteByte(0x1b)
|
||||
skip = 1
|
||||
}
|
||||
case '"':
|
||||
b.WriteByte(0x22)
|
||||
skip = 1
|
||||
@@ -807,9 +815,11 @@ func (p *parser) replaceEscapes(it item, str string) string {
|
||||
// The lexer guarantees the correct number of characters are present;
|
||||
// don't need to check here.
|
||||
case 'x':
|
||||
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4])
|
||||
b.WriteRune(escaped)
|
||||
skip = 3
|
||||
if p.tomlNext {
|
||||
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4])
|
||||
b.WriteRune(escaped)
|
||||
skip = 3
|
||||
}
|
||||
case 'u':
|
||||
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+6])
|
||||
b.WriteRune(escaped)
|
||||
|
||||
2
vendor/github.com/clipperhouse/stringish/.gitignore
generated
vendored
2
vendor/github.com/clipperhouse/stringish/.gitignore
generated
vendored
@@ -1,2 +0,0 @@
|
||||
.DS_Store
|
||||
*.test
|
||||
64
vendor/github.com/clipperhouse/stringish/README.md
generated
vendored
64
vendor/github.com/clipperhouse/stringish/README.md
generated
vendored
@@ -1,64 +0,0 @@
|
||||
# stringish
|
||||
|
||||
A small Go module that provides a generic type constraint for “string-like”
|
||||
data, and a utf8 package that works with both strings and byte slices
|
||||
without conversions.
|
||||
|
||||
```go
|
||||
type Interface interface {
|
||||
~[]byte | ~string
|
||||
}
|
||||
```
|
||||
|
||||
[](https://pkg.go.dev/github.com/clipperhouse/stringish/utf8)
|
||||
[](https://github.com/clipperhouse/stringish/actions/workflows/gotest.yml)
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
go get github.com/clipperhouse/stringish
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/clipperhouse/stringish"
|
||||
"github.com/clipperhouse/stringish/utf8"
|
||||
)
|
||||
|
||||
s := "Hello, 世界"
|
||||
r, size := utf8.DecodeRune(s) // not DecodeRuneInString 🎉
|
||||
|
||||
b := []byte("Hello, 世界")
|
||||
r, size = utf8.DecodeRune(b) // same API!
|
||||
|
||||
func MyFoo[T stringish.Interface](s T) T {
|
||||
// pass a string or a []byte
|
||||
// iterate, slice, transform, whatever
|
||||
}
|
||||
```
|
||||
|
||||
## Motivation
|
||||
|
||||
Sometimes we want APIs to accept `string` or `[]byte` without having to convert
|
||||
between those types. That conversion usually allocates!
|
||||
|
||||
By implementing with `stringish.Interface`, we can have a single API, and
|
||||
single implementation for both types: one `Foo` instead of `Foo` and
|
||||
`FooString`.
|
||||
|
||||
We have converted the
|
||||
[`unicode/utf8` package](https://github.com/clipperhouse/stringish/blob/main/utf8/utf8.go)
|
||||
as an example -- note the absence of`*InString` funcs. We might look at `x/text`
|
||||
next.
|
||||
|
||||
## Used by
|
||||
|
||||
- clipperhouse/uax29: [stringish trie](https://github.com/clipperhouse/uax29/blob/master/graphemes/trie.go#L27), [stringish iterator](https://github.com/clipperhouse/uax29/blob/master/internal/iterators/iterator.go#L9), [stringish SplitFunc](https://github.com/clipperhouse/uax29/blob/master/graphemes/splitfunc.go#L21)
|
||||
|
||||
- [clipperhouse/displaywidth](https://github.com/clipperhouse/displaywidth)
|
||||
|
||||
## Prior discussion
|
||||
|
||||
- [Consideration of similar by the Go team](https://github.com/golang/go/issues/48643)
|
||||
5
vendor/github.com/clipperhouse/stringish/interface.go
generated
vendored
5
vendor/github.com/clipperhouse/stringish/interface.go
generated
vendored
@@ -1,5 +0,0 @@
|
||||
package stringish
|
||||
|
||||
type Interface interface {
|
||||
~[]byte | ~string
|
||||
}
|
||||
94
vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
generated
vendored
94
vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
generated
vendored
@@ -1,94 +0,0 @@
|
||||
An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode version 15.0.0.
|
||||
|
||||
[](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes)
|
||||

|
||||

|
||||
|
||||
## Quick start
|
||||
|
||||
```
|
||||
go get "github.com/clipperhouse/uax29/v2/graphemes"
|
||||
```
|
||||
|
||||
```go
|
||||
import "github.com/clipperhouse/uax29/v2/graphemes"
|
||||
|
||||
text := "Hello, 世界. Nice dog! 👍🐶"
|
||||
|
||||
tokens := graphemes.FromString(text)
|
||||
|
||||
for tokens.Next() { // Next() returns true until end of data
|
||||
fmt.Println(tokens.Value()) // Do something with the current grapheme
|
||||
}
|
||||
```
|
||||
|
||||
_A grapheme is a “single visible character”, which might be a simple as a single letter, or a complex emoji that consists of several Unicode code points._
|
||||
|
||||
## Conformance
|
||||
|
||||
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29).
|
||||
|
||||

|
||||

|
||||
|
||||
## APIs
|
||||
|
||||
### If you have a `string`
|
||||
|
||||
```go
|
||||
text := "Hello, 世界. Nice dog! 👍🐶"
|
||||
|
||||
tokens := graphemes.FromString(text)
|
||||
|
||||
for tokens.Next() { // Next() returns true until end of data
|
||||
fmt.Println(tokens.Value()) // Do something with the current grapheme
|
||||
}
|
||||
```
|
||||
|
||||
### If you have an `io.Reader`
|
||||
|
||||
`FromReader` embeds a [`bufio.Scanner`](https://pkg.go.dev/bufio#Scanner), so just use those methods.
|
||||
|
||||
```go
|
||||
r := getYourReader() // from a file or network maybe
|
||||
tokens := graphemes.FromReader(r)
|
||||
|
||||
for tokens.Scan() { // Scan() returns true until error or EOF
|
||||
fmt.Println(tokens.Text()) // Do something with the current grapheme
|
||||
}
|
||||
|
||||
if tokens.Err() != nil { // Check the error
|
||||
log.Fatal(tokens.Err())
|
||||
}
|
||||
```
|
||||
|
||||
### If you have a `[]byte`
|
||||
|
||||
```go
|
||||
b := []byte("Hello, 世界. Nice dog! 👍🐶")
|
||||
|
||||
tokens := graphemes.FromBytes(b)
|
||||
|
||||
for tokens.Next() { // Next() returns true until end of data
|
||||
fmt.Println(tokens.Value()) // Do something with the current grapheme
|
||||
}
|
||||
```
|
||||
|
||||
### Benchmarks
|
||||
|
||||
On a Mac M2 laptop, we see around 200MB/s, or around 100 million graphemes per second, and no allocations.
|
||||
|
||||
```
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
pkg: github.com/clipperhouse/uax29/graphemes/comparative
|
||||
cpu: Apple M2
|
||||
BenchmarkGraphemes/clipperhouse/uax29-8 173805 ns/op 201.16 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkGraphemes/rivo/uniseg-8 2045128 ns/op 17.10 MB/s 0 B/op 0 allocs/op
|
||||
```
|
||||
|
||||
### Invalid inputs
|
||||
|
||||
Invalid UTF-8 input is considered undefined behavior. We test to ensure that bad inputs will not cause pathological outcomes, such as a panic or infinite loop. Callers should expect “garbage-in, garbage-out”.
|
||||
|
||||
Your pipeline should probably include a call to [`utf8.Valid()`](https://pkg.go.dev/unicode/utf8#Valid).
|
||||
31
vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
generated
vendored
31
vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
generated
vendored
@@ -1,31 +0,0 @@
|
||||
package graphemes
|
||||
|
||||
import (
|
||||
"github.com/clipperhouse/stringish"
|
||||
"github.com/clipperhouse/uax29/v2/internal/iterators"
|
||||
)
|
||||
|
||||
type Iterator[T stringish.Interface] struct {
|
||||
*iterators.Iterator[T]
|
||||
}
|
||||
|
||||
var (
|
||||
splitFuncString = splitFunc[string]
|
||||
splitFuncBytes = splitFunc[[]byte]
|
||||
)
|
||||
|
||||
// FromString returns an iterator for the grapheme clusters in the input string.
|
||||
// Iterate while Next() is true, and access the grapheme via Value().
|
||||
func FromString(s string) Iterator[string] {
|
||||
return Iterator[string]{
|
||||
iterators.New(splitFuncString, s),
|
||||
}
|
||||
}
|
||||
|
||||
// FromBytes returns an iterator for the grapheme clusters in the input bytes.
|
||||
// Iterate while Next() is true, and access the grapheme via Value().
|
||||
func FromBytes(b []byte) Iterator[[]byte] {
|
||||
return Iterator[[]byte]{
|
||||
iterators.New(splitFuncBytes, b),
|
||||
}
|
||||
}
|
||||
25
vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go
generated
vendored
25
vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go
generated
vendored
@@ -1,25 +0,0 @@
|
||||
// Package graphemes implements Unicode grapheme cluster boundaries: https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
|
||||
package graphemes
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Scanner struct {
|
||||
*bufio.Scanner
|
||||
}
|
||||
|
||||
// FromReader returns a Scanner, to split graphemes per
|
||||
// https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
|
||||
//
|
||||
// It embeds a [bufio.Scanner], so you can use its methods.
|
||||
//
|
||||
// Iterate through graphemes by calling Scan() until false, then check Err().
|
||||
func FromReader(r io.Reader) *Scanner {
|
||||
sc := bufio.NewScanner(r)
|
||||
sc.Split(SplitFunc)
|
||||
return &Scanner{
|
||||
Scanner: sc,
|
||||
}
|
||||
}
|
||||
174
vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
generated
vendored
174
vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
generated
vendored
@@ -1,174 +0,0 @@
|
||||
package graphemes
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
||||
"github.com/clipperhouse/stringish"
|
||||
)
|
||||
|
||||
// is determines if lookup intersects propert(ies)
|
||||
func (lookup property) is(properties property) bool {
|
||||
return (lookup & properties) != 0
|
||||
}
|
||||
|
||||
const _Ignore = _Extend
|
||||
|
||||
// SplitFunc is a bufio.SplitFunc implementation of Unicode grapheme cluster segmentation, for use with bufio.Scanner.
|
||||
//
|
||||
// See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
|
||||
var SplitFunc bufio.SplitFunc = splitFunc[[]byte]
|
||||
|
||||
func splitFunc[T stringish.Interface](data T, atEOF bool) (advance int, token T, err error) {
|
||||
var empty T
|
||||
if len(data) == 0 {
|
||||
return 0, empty, nil
|
||||
}
|
||||
|
||||
// These vars are stateful across loop iterations
|
||||
var pos int
|
||||
var lastExIgnore property = 0 // "last excluding ignored categories"
|
||||
var lastLastExIgnore property = 0 // "last one before that"
|
||||
var regionalIndicatorCount int
|
||||
|
||||
// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property
|
||||
// to the right of the ×, from which we look back or forward
|
||||
|
||||
current, w := lookup(data[pos:])
|
||||
if w == 0 {
|
||||
if !atEOF {
|
||||
// Rune extends past current data, request more
|
||||
return 0, empty, nil
|
||||
}
|
||||
pos = len(data)
|
||||
return pos, data[:pos], nil
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB1
|
||||
// Start of text always advances
|
||||
pos += w
|
||||
|
||||
for {
|
||||
eot := pos == len(data) // "end of text"
|
||||
|
||||
if eot {
|
||||
if !atEOF {
|
||||
// Token extends past current data, request more
|
||||
return 0, empty, nil
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB2
|
||||
break
|
||||
}
|
||||
|
||||
/*
|
||||
We've switched the evaluation order of GB1↓ and GB2↑. It's ok:
|
||||
because we've checked for len(data) at the top of this function,
|
||||
sot and eot are mutually exclusive, order doesn't matter.
|
||||
*/
|
||||
|
||||
// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property
|
||||
// to the right of the ×, from which we look back or forward
|
||||
|
||||
// Remember previous properties to avoid lookups/lookbacks
|
||||
last := current
|
||||
if !last.is(_Ignore) {
|
||||
lastLastExIgnore = lastExIgnore
|
||||
lastExIgnore = last
|
||||
}
|
||||
|
||||
current, w = lookup(data[pos:])
|
||||
if w == 0 {
|
||||
if atEOF {
|
||||
// Just return the bytes, we can't do anything with them
|
||||
pos = len(data)
|
||||
break
|
||||
}
|
||||
// Rune extends past current data, request more
|
||||
return 0, empty, nil
|
||||
}
|
||||
|
||||
// Optimization: no rule can possibly apply
|
||||
if current|last == 0 { // i.e. both are zero
|
||||
break
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB3
|
||||
if current.is(_LF) && last.is(_CR) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB4
|
||||
// https://unicode.org/reports/tr29/#GB5
|
||||
if (current | last).is(_Control | _CR | _LF) {
|
||||
break
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB6
|
||||
if current.is(_L|_V|_LV|_LVT) && last.is(_L) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB7
|
||||
if current.is(_V|_T) && last.is(_LV|_V) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB8
|
||||
if current.is(_T) && last.is(_LVT|_T) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9
|
||||
if current.is(_Extend | _ZWJ) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9a
|
||||
if current.is(_SpacingMark) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9b
|
||||
if last.is(_Prepend) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9c
|
||||
// TODO(clipperhouse):
|
||||
// It appears to be added in Unicode 15.1.0:
|
||||
// https://unicode.org/versions/Unicode15.1.0/#Migration
|
||||
// This package currently supports Unicode 15.0.0, so
|
||||
// out of scope for now
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB11
|
||||
if current.is(_ExtendedPictographic) && last.is(_ZWJ) && lastLastExIgnore.is(_ExtendedPictographic) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB12
|
||||
// https://unicode.org/reports/tr29/#GB13
|
||||
if (current & last).is(_RegionalIndicator) {
|
||||
regionalIndicatorCount++
|
||||
|
||||
odd := regionalIndicatorCount%2 == 1
|
||||
if odd {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If we fall through all the above rules, it's a grapheme cluster break
|
||||
break
|
||||
}
|
||||
|
||||
// Return token
|
||||
return pos, data[:pos], nil
|
||||
}
|
||||
1409
vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
generated
vendored
1409
vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
generated
vendored
File diff suppressed because it is too large
Load Diff
100
vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
generated
vendored
100
vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
generated
vendored
@@ -1,100 +0,0 @@
|
||||
package iterators
|
||||
|
||||
import "github.com/clipperhouse/stringish"
|
||||
|
||||
type SplitFunc[T stringish.Interface] func(T, bool) (int, T, error)
|
||||
|
||||
// Iterator is a generic iterator for words that are either []byte or string.
|
||||
// Iterate while Next() is true, and access the word via Value().
|
||||
type Iterator[T stringish.Interface] struct {
|
||||
split SplitFunc[T]
|
||||
data T
|
||||
start int
|
||||
pos int
|
||||
}
|
||||
|
||||
// New creates a new Iterator for the given data and SplitFunc.
|
||||
func New[T stringish.Interface](split SplitFunc[T], data T) *Iterator[T] {
|
||||
return &Iterator[T]{
|
||||
split: split,
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// SetText sets the text for the iterator to operate on, and resets all state.
|
||||
func (iter *Iterator[T]) SetText(data T) {
|
||||
iter.data = data
|
||||
iter.start = 0
|
||||
iter.pos = 0
|
||||
}
|
||||
|
||||
// Split sets the SplitFunc for the Iterator.
|
||||
func (iter *Iterator[T]) Split(split SplitFunc[T]) {
|
||||
iter.split = split
|
||||
}
|
||||
|
||||
// Next advances the iterator to the next token. It returns false when there
|
||||
// are no remaining tokens or an error occurred.
|
||||
func (iter *Iterator[T]) Next() bool {
|
||||
if iter.pos == len(iter.data) {
|
||||
return false
|
||||
}
|
||||
if iter.pos > len(iter.data) {
|
||||
panic("SplitFunc advanced beyond the end of the data")
|
||||
}
|
||||
|
||||
iter.start = iter.pos
|
||||
|
||||
advance, _, err := iter.split(iter.data[iter.pos:], true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if advance <= 0 {
|
||||
panic("SplitFunc returned a zero or negative advance")
|
||||
}
|
||||
|
||||
iter.pos += advance
|
||||
if iter.pos > len(iter.data) {
|
||||
panic("SplitFunc advanced beyond the end of the data")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Value returns the current token.
|
||||
func (iter *Iterator[T]) Value() T {
|
||||
return iter.data[iter.start:iter.pos]
|
||||
}
|
||||
|
||||
// Start returns the byte position of the current token in the original data.
|
||||
func (iter *Iterator[T]) Start() int {
|
||||
return iter.start
|
||||
}
|
||||
|
||||
// End returns the byte position after the current token in the original data.
|
||||
func (iter *Iterator[T]) End() int {
|
||||
return iter.pos
|
||||
}
|
||||
|
||||
// Reset resets the iterator to the beginning of the data.
|
||||
func (iter *Iterator[T]) Reset() {
|
||||
iter.start = 0
|
||||
iter.pos = 0
|
||||
}
|
||||
|
||||
func (iter *Iterator[T]) First() T {
|
||||
if len(iter.data) == 0 {
|
||||
return iter.data
|
||||
}
|
||||
advance, _, err := iter.split(iter.data, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if advance <= 0 {
|
||||
panic("SplitFunc returned a zero or negative advance")
|
||||
}
|
||||
if advance > len(iter.data) {
|
||||
panic("SplitFunc advanced beyond the end of the data")
|
||||
}
|
||||
return iter.data[:advance]
|
||||
}
|
||||
111
vendor/github.com/containerd/stargz-snapshotter/estargz/build.go
generated
vendored
111
vendor/github.com/containerd/stargz-snapshotter/estargz/build.go
generated
vendored
@@ -35,7 +35,6 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/containerd/stargz-snapshotter/estargz/errorutil"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
@@ -43,8 +42,6 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type GzipHelperFunc func(io.Reader) (io.ReadCloser, error)
|
||||
|
||||
type options struct {
|
||||
chunkSize int
|
||||
compressionLevel int
|
||||
@@ -53,7 +50,6 @@ type options struct {
|
||||
compression Compression
|
||||
ctx context.Context
|
||||
minChunkSize int
|
||||
gzipHelperFunc GzipHelperFunc
|
||||
}
|
||||
|
||||
type Option func(o *options) error
|
||||
@@ -131,25 +127,11 @@ func WithMinChunkSize(minChunkSize int) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithGzipHelperFunc option specifies a custom function to decompress gzip-compressed layers.
|
||||
// When a gzip-compressed layer is detected, this function will be used instead of the
|
||||
// Go standard library gzip decompression for better performance.
|
||||
// The function should take an io.Reader as input and return an io.ReadCloser.
|
||||
// If nil, the Go standard library gzip.NewReader will be used.
|
||||
func WithGzipHelperFunc(gzipHelperFunc GzipHelperFunc) Option {
|
||||
return func(o *options) error {
|
||||
o.gzipHelperFunc = gzipHelperFunc
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Blob is an eStargz blob.
|
||||
type Blob struct {
|
||||
io.ReadCloser
|
||||
diffID digest.Digester
|
||||
tocDigest digest.Digest
|
||||
readCompleted *atomic.Bool
|
||||
uncompressedSize *atomic.Int64
|
||||
diffID digest.Digester
|
||||
tocDigest digest.Digest
|
||||
}
|
||||
|
||||
// DiffID returns the digest of uncompressed blob.
|
||||
@@ -163,19 +145,6 @@ func (b *Blob) TOCDigest() digest.Digest {
|
||||
return b.tocDigest
|
||||
}
|
||||
|
||||
// UncompressedSize returns the size of uncompressed blob.
|
||||
// UncompressedSize should only be called after the blob has been fully read.
|
||||
func (b *Blob) UncompressedSize() (int64, error) {
|
||||
switch {
|
||||
case b.uncompressedSize == nil || b.readCompleted == nil:
|
||||
return -1, fmt.Errorf("readCompleted or uncompressedSize is not initialized")
|
||||
case !b.readCompleted.Load():
|
||||
return -1, fmt.Errorf("called UncompressedSize before the blob has been fully read")
|
||||
default:
|
||||
return b.uncompressedSize.Load(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Build builds an eStargz blob which is an extended version of stargz, from a blob (gzip, zstd
|
||||
// or plain tar) passed through the argument. If there are some prioritized files are listed in
|
||||
// the option, these files are grouped as "prioritized" and can be used for runtime optimization
|
||||
@@ -217,7 +186,7 @@ func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) {
|
||||
rErr = fmt.Errorf("error from context %q: %w", cErr, rErr)
|
||||
}
|
||||
}()
|
||||
tarBlob, err := decompressBlob(tarBlob, layerFiles, opts.gzipHelperFunc)
|
||||
tarBlob, err := decompressBlob(tarBlob, layerFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -283,28 +252,17 @@ func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) {
|
||||
}
|
||||
diffID := digest.Canonical.Digester()
|
||||
pr, pw := io.Pipe()
|
||||
readCompleted := new(atomic.Bool)
|
||||
uncompressedSize := new(atomic.Int64)
|
||||
go func() {
|
||||
var size int64
|
||||
var decompressFunc func(io.Reader) (io.ReadCloser, error)
|
||||
if _, ok := opts.compression.(*gzipCompression); ok && opts.gzipHelperFunc != nil {
|
||||
decompressFunc = opts.gzipHelperFunc
|
||||
} else {
|
||||
decompressFunc = opts.compression.Reader
|
||||
}
|
||||
decompressR, err := decompressFunc(io.TeeReader(io.MultiReader(append(rs, tocAndFooter)...), pw))
|
||||
r, err := opts.compression.Reader(io.TeeReader(io.MultiReader(append(rs, tocAndFooter)...), pw))
|
||||
if err != nil {
|
||||
pw.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
defer decompressR.Close()
|
||||
if size, err = io.Copy(diffID.Hash(), decompressR); err != nil {
|
||||
defer r.Close()
|
||||
if _, err := io.Copy(diffID.Hash(), r); err != nil {
|
||||
pw.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
uncompressedSize.Store(size)
|
||||
readCompleted.Store(true)
|
||||
pw.Close()
|
||||
}()
|
||||
return &Blob{
|
||||
@@ -312,10 +270,8 @@ func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) {
|
||||
Reader: pr,
|
||||
closeFunc: layerFiles.CleanupAll,
|
||||
},
|
||||
tocDigest: tocDgst,
|
||||
diffID: diffID,
|
||||
readCompleted: readCompleted,
|
||||
uncompressedSize: uncompressedSize,
|
||||
tocDigest: tocDgst,
|
||||
diffID: diffID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -410,9 +366,8 @@ func sortEntries(in io.ReaderAt, prioritized []string, missedPrioritized *[]stri
|
||||
|
||||
// Sort the tar file respecting to the prioritized files list.
|
||||
sorted := &tarFile{}
|
||||
picked := make(map[string]struct{})
|
||||
for _, l := range prioritized {
|
||||
if err := moveRec(l, intar, sorted, picked); err != nil {
|
||||
if err := moveRec(l, intar, sorted); err != nil {
|
||||
if errors.Is(err, errNotFound) && missedPrioritized != nil {
|
||||
*missedPrioritized = append(*missedPrioritized, l)
|
||||
continue // allow not found
|
||||
@@ -440,8 +395,8 @@ func sortEntries(in io.ReaderAt, prioritized []string, missedPrioritized *[]stri
|
||||
})
|
||||
}
|
||||
|
||||
// Dump prioritized entries followed by the rest entries while skipping picked ones.
|
||||
return append(sorted.dump(nil), intar.dump(picked)...), nil
|
||||
// Dump all entry and concatinate them.
|
||||
return append(sorted.dump(), intar.dump()...), nil
|
||||
}
|
||||
|
||||
// readerFromEntries returns a reader of tar archive that contains entries passed
|
||||
@@ -503,42 +458,36 @@ func importTar(in io.ReaderAt) (*tarFile, error) {
|
||||
return tf, nil
|
||||
}
|
||||
|
||||
func moveRec(name string, in *tarFile, out *tarFile, picked map[string]struct{}) error {
|
||||
func moveRec(name string, in *tarFile, out *tarFile) error {
|
||||
name = cleanEntryName(name)
|
||||
if name == "" { // root directory. stop recursion.
|
||||
if e, ok := in.get(name); ok {
|
||||
// entry of the root directory exists. we should move it as well.
|
||||
// this case will occur if tar entries are prefixed with "./", "/", etc.
|
||||
if _, done := picked[name]; !done {
|
||||
out.add(e)
|
||||
picked[name] = struct{}{}
|
||||
}
|
||||
out.add(e)
|
||||
in.remove(name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_, okIn := in.get(name)
|
||||
_, okOut := out.get(name)
|
||||
_, okPicked := picked[name]
|
||||
if !okIn && !okOut && !okPicked {
|
||||
if !okIn && !okOut {
|
||||
return fmt.Errorf("file: %q: %w", name, errNotFound)
|
||||
}
|
||||
|
||||
parent, _ := path.Split(strings.TrimSuffix(name, "/"))
|
||||
if err := moveRec(parent, in, out, picked); err != nil {
|
||||
if err := moveRec(parent, in, out); err != nil {
|
||||
return err
|
||||
}
|
||||
if e, ok := in.get(name); ok && e.header.Typeflag == tar.TypeLink {
|
||||
if err := moveRec(e.header.Linkname, in, out, picked); err != nil {
|
||||
if err := moveRec(e.header.Linkname, in, out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, done := picked[name]; done {
|
||||
return nil
|
||||
}
|
||||
if e, ok := in.get(name); ok {
|
||||
out.add(e)
|
||||
picked[name] = struct{}{}
|
||||
in.remove(name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -584,18 +533,8 @@ func (f *tarFile) get(name string) (e *entry, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (f *tarFile) dump(skip map[string]struct{}) []*entry {
|
||||
if len(skip) == 0 {
|
||||
return f.stream
|
||||
}
|
||||
var out []*entry
|
||||
for _, e := range f.stream {
|
||||
if _, ok := skip[cleanEntryName(e.header.Name)]; ok {
|
||||
continue
|
||||
}
|
||||
out = append(out, e)
|
||||
}
|
||||
return out
|
||||
func (f *tarFile) dump() []*entry {
|
||||
return f.stream
|
||||
}
|
||||
|
||||
type readCloser struct {
|
||||
@@ -710,7 +649,7 @@ func (cr *countReadSeeker) currentPos() int64 {
|
||||
return *cr.cPos
|
||||
}
|
||||
|
||||
func decompressBlob(org *io.SectionReader, tmp *tempFiles, gzipHelperFunc GzipHelperFunc) (*io.SectionReader, error) {
|
||||
func decompressBlob(org *io.SectionReader, tmp *tempFiles) (*io.SectionReader, error) {
|
||||
if org.Size() < 4 {
|
||||
return org, nil
|
||||
}
|
||||
@@ -721,13 +660,7 @@ func decompressBlob(org *io.SectionReader, tmp *tempFiles, gzipHelperFunc GzipHe
|
||||
var dR io.Reader
|
||||
if bytes.Equal([]byte{0x1F, 0x8B, 0x08}, src[:3]) {
|
||||
// gzip
|
||||
var dgR io.ReadCloser
|
||||
var err error
|
||||
if gzipHelperFunc != nil {
|
||||
dgR, err = gzipHelperFunc(io.NewSectionReader(org, 0, org.Size()))
|
||||
} else {
|
||||
dgR, err = gzip.NewReader(io.NewSectionReader(org, 0, org.Size()))
|
||||
}
|
||||
dgR, err := gzip.NewReader(io.NewSectionReader(org, 0, org.Size()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
11
vendor/github.com/containerd/stargz-snapshotter/estargz/estargz.go
generated
vendored
11
vendor/github.com/containerd/stargz-snapshotter/estargz/estargz.go
generated
vendored
@@ -237,7 +237,7 @@ func (r *Reader) initFields() error {
|
||||
if ent.Gname != "" {
|
||||
gname[ent.GID] = ent.Gname
|
||||
} else {
|
||||
ent.Gname = gname[ent.GID]
|
||||
ent.Gname = uname[ent.GID]
|
||||
}
|
||||
|
||||
ent.modTime, _ = time.Parse(time.RFC3339, ent.ModTime3339)
|
||||
@@ -307,15 +307,6 @@ func (r *Reader) initFields() error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.m) == 0 {
|
||||
r.m[""] = &TOCEntry{
|
||||
Name: "",
|
||||
Type: "dir",
|
||||
Mode: 0755,
|
||||
NumLink: 1,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
155
vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go
generated
vendored
155
vendor/github.com/containerd/stargz-snapshotter/estargz/testutil.go
generated
vendored
@@ -38,6 +38,7 @@ import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/stargz-snapshotter/estargz/errorutil"
|
||||
@@ -48,48 +49,16 @@ import (
|
||||
// TestingController is Compression with some helper methods necessary for testing.
|
||||
type TestingController interface {
|
||||
Compression
|
||||
TestStreams(t TestingT, b []byte, streams []int64)
|
||||
DiffIDOf(TestingT, []byte) string
|
||||
TestStreams(t *testing.T, b []byte, streams []int64)
|
||||
DiffIDOf(*testing.T, []byte) string
|
||||
String() string
|
||||
}
|
||||
|
||||
// TestingT is the minimal set of testing.T required to run the
|
||||
// tests defined in CompressionTestSuite. This interface exists to prevent
|
||||
// leaking the testing package from being exposed outside tests.
|
||||
type TestingT interface {
|
||||
Errorf(format string, args ...any)
|
||||
FailNow()
|
||||
Failed() bool
|
||||
Fatal(args ...any)
|
||||
Fatalf(format string, args ...any)
|
||||
Logf(format string, args ...any)
|
||||
Parallel()
|
||||
}
|
||||
|
||||
// Runner allows running subtests of TestingT. This exists instead of adding
|
||||
// a Run method to TestingT interface because the Run implementation of
|
||||
// testing.T would not satisfy the interface.
|
||||
type Runner func(t TestingT, name string, fn func(t TestingT))
|
||||
|
||||
type TestRunner struct {
|
||||
TestingT
|
||||
Runner Runner
|
||||
}
|
||||
|
||||
func (r *TestRunner) Run(name string, run func(*TestRunner)) {
|
||||
r.Runner(r.TestingT, name, func(t TestingT) {
|
||||
run(&TestRunner{TestingT: t, Runner: r.Runner})
|
||||
})
|
||||
}
|
||||
|
||||
// CompressionTestSuite tests this pkg with controllers can build valid eStargz blobs and parse them.
|
||||
func CompressionTestSuite(t *TestRunner, controllers ...TestingControllerFactory) {
|
||||
t.Run("testBuild", func(t *TestRunner) { t.Parallel(); testBuild(t, controllers...) })
|
||||
t.Run("testDigestAndVerify", func(t *TestRunner) {
|
||||
t.Parallel()
|
||||
testDigestAndVerify(t, controllers...)
|
||||
})
|
||||
t.Run("testWriteAndOpen", func(t *TestRunner) { t.Parallel(); testWriteAndOpen(t, controllers...) })
|
||||
func CompressionTestSuite(t *testing.T, controllers ...TestingControllerFactory) {
|
||||
t.Run("testBuild", func(t *testing.T) { t.Parallel(); testBuild(t, controllers...) })
|
||||
t.Run("testDigestAndVerify", func(t *testing.T) { t.Parallel(); testDigestAndVerify(t, controllers...) })
|
||||
t.Run("testWriteAndOpen", func(t *testing.T) { t.Parallel(); testWriteAndOpen(t, controllers...) })
|
||||
}
|
||||
|
||||
type TestingControllerFactory func() TestingController
|
||||
@@ -110,7 +79,7 @@ var allowedPrefix = [4]string{"", "./", "/", "../"}
|
||||
|
||||
// testBuild tests the resulting stargz blob built by this pkg has the same
|
||||
// contents as the normal stargz blob.
|
||||
func testBuild(t *TestRunner, controllers ...TestingControllerFactory) {
|
||||
func testBuild(t *testing.T, controllers ...TestingControllerFactory) {
|
||||
tests := []struct {
|
||||
name string
|
||||
chunkSize int
|
||||
@@ -196,7 +165,7 @@ func testBuild(t *TestRunner, controllers ...TestingControllerFactory) {
|
||||
prefix := prefix
|
||||
for _, minChunkSize := range tt.minChunkSize {
|
||||
minChunkSize := minChunkSize
|
||||
t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,src=%d,format=%s,minChunkSize=%d", newCL(), prefix, srcCompression, srcTarFormat, minChunkSize), func(t *TestRunner) {
|
||||
t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,src=%d,format=%s,minChunkSize=%d", newCL(), prefix, srcCompression, srcTarFormat, minChunkSize), func(t *testing.T) {
|
||||
tarBlob := buildTar(t, tt.in, prefix, srcTarFormat)
|
||||
// Test divideEntries()
|
||||
entries, err := sortEntries(tarBlob, nil, nil) // identical order
|
||||
@@ -296,7 +265,7 @@ func testBuild(t *TestRunner, controllers ...TestingControllerFactory) {
|
||||
}
|
||||
}
|
||||
|
||||
func isSameTarGz(t TestingT, cla TestingController, a []byte, clb TestingController, b []byte) bool {
|
||||
func isSameTarGz(t *testing.T, cla TestingController, a []byte, clb TestingController, b []byte) bool {
|
||||
aGz, err := cla.Reader(bytes.NewReader(a))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read A")
|
||||
@@ -356,7 +325,7 @@ func isSameTarGz(t TestingT, cla TestingController, a []byte, clb TestingControl
|
||||
return true
|
||||
}
|
||||
|
||||
func isSameVersion(t TestingT, cla TestingController, a []byte, clb TestingController, b []byte) bool {
|
||||
func isSameVersion(t *testing.T, cla TestingController, a []byte, clb TestingController, b []byte) bool {
|
||||
aJTOC, _, err := parseStargz(io.NewSectionReader(bytes.NewReader(a), 0, int64(len(a))), cla)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse A: %v", err)
|
||||
@@ -370,7 +339,7 @@ func isSameVersion(t TestingT, cla TestingController, a []byte, clb TestingContr
|
||||
return aJTOC.Version == bJTOC.Version
|
||||
}
|
||||
|
||||
func isSameEntries(t TestingT, a, b *Reader) bool {
|
||||
func isSameEntries(t *testing.T, a, b *Reader) bool {
|
||||
aroot, ok := a.Lookup("")
|
||||
if !ok {
|
||||
t.Fatalf("failed to get root of A")
|
||||
@@ -384,7 +353,7 @@ func isSameEntries(t TestingT, a, b *Reader) bool {
|
||||
return contains(t, aEntry, bEntry) && contains(t, bEntry, aEntry)
|
||||
}
|
||||
|
||||
func compressBlob(t TestingT, src *io.SectionReader, srcCompression int) *io.SectionReader {
|
||||
func compressBlob(t *testing.T, src *io.SectionReader, srcCompression int) *io.SectionReader {
|
||||
buf := new(bytes.Buffer)
|
||||
var w io.WriteCloser
|
||||
var err error
|
||||
@@ -418,7 +387,7 @@ type stargzEntry struct {
|
||||
|
||||
// contains checks if all child entries in "b" are also contained in "a".
|
||||
// This function also checks if the files/chunks contain the same contents among "a" and "b".
|
||||
func contains(t TestingT, a, b stargzEntry) bool {
|
||||
func contains(t *testing.T, a, b stargzEntry) bool {
|
||||
ae, ar := a.e, a.r
|
||||
be, br := b.e, b.r
|
||||
t.Logf("Comparing: %q vs %q", ae.Name, be.Name)
|
||||
@@ -529,7 +498,7 @@ func equalEntry(a, b *TOCEntry) bool {
|
||||
a.Digest == b.Digest
|
||||
}
|
||||
|
||||
func readOffset(t TestingT, r *io.SectionReader, offset int64, e stargzEntry) ([]byte, int64, bool) {
|
||||
func readOffset(t *testing.T, r *io.SectionReader, offset int64, e stargzEntry) ([]byte, int64, bool) {
|
||||
ce, ok := e.r.ChunkEntryForOffset(e.e.Name, offset)
|
||||
if !ok {
|
||||
return nil, 0, false
|
||||
@@ -548,7 +517,7 @@ func readOffset(t TestingT, r *io.SectionReader, offset int64, e stargzEntry) ([
|
||||
return data[:n], offset + ce.ChunkSize, true
|
||||
}
|
||||
|
||||
func dumpTOCJSON(t TestingT, tocJSON *JTOC) string {
|
||||
func dumpTOCJSON(t *testing.T, tocJSON *JTOC) string {
|
||||
jtocData, err := json.Marshal(*tocJSON)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal TOC JSON: %v", err)
|
||||
@@ -562,19 +531,20 @@ func dumpTOCJSON(t TestingT, tocJSON *JTOC) string {
|
||||
|
||||
const chunkSize = 3
|
||||
|
||||
type check func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory)
|
||||
// type check func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, compressionLevel int)
|
||||
type check func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory)
|
||||
|
||||
// testDigestAndVerify runs specified checks against sample stargz blobs.
|
||||
func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory) {
|
||||
func testDigestAndVerify(t *testing.T, controllers ...TestingControllerFactory) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tarInit func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry)
|
||||
tarInit func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry)
|
||||
checks []check
|
||||
minChunkSize []int
|
||||
}{
|
||||
{
|
||||
name: "no-regfile",
|
||||
tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) {
|
||||
tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) {
|
||||
return tarOf(
|
||||
dir("test/"),
|
||||
)
|
||||
@@ -589,7 +559,7 @@ func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory)
|
||||
},
|
||||
{
|
||||
name: "small-files",
|
||||
tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) {
|
||||
tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) {
|
||||
return tarOf(
|
||||
regDigest(t, "baz.txt", "", dgstMap),
|
||||
regDigest(t, "foo.txt", "a", dgstMap),
|
||||
@@ -613,7 +583,7 @@ func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory)
|
||||
},
|
||||
{
|
||||
name: "big-files",
|
||||
tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) {
|
||||
tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) {
|
||||
return tarOf(
|
||||
regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap),
|
||||
regDigest(t, "foo.txt", "a", dgstMap),
|
||||
@@ -637,7 +607,7 @@ func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory)
|
||||
{
|
||||
name: "with-non-regfiles",
|
||||
minChunkSize: []int{0, 64000},
|
||||
tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) {
|
||||
tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) {
|
||||
return tarOf(
|
||||
regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap),
|
||||
regDigest(t, "foo.txt", "a", dgstMap),
|
||||
@@ -684,7 +654,7 @@ func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory)
|
||||
srcTarFormat := srcTarFormat
|
||||
for _, minChunkSize := range tt.minChunkSize {
|
||||
minChunkSize := minChunkSize
|
||||
t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,format=%s,minChunkSize=%d", newCL(), prefix, srcTarFormat, minChunkSize), func(t *TestRunner) {
|
||||
t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,format=%s,minChunkSize=%d", newCL(), prefix, srcTarFormat, minChunkSize), func(t *testing.T) {
|
||||
// Get original tar file and chunk digests
|
||||
dgstMap := make(map[string]digest.Digest)
|
||||
tarBlob := buildTar(t, tt.tarInit(t, dgstMap), prefix, srcTarFormat)
|
||||
@@ -720,7 +690,7 @@ func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory)
|
||||
// checkStargzTOC checks the TOC JSON of the passed stargz has the expected
|
||||
// digest and contains valid chunks. It walks all entries in the stargz and
|
||||
// checks all chunk digests stored to the TOC JSON match the actual contents.
|
||||
func checkStargzTOC(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
|
||||
func checkStargzTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
|
||||
sgz, err := Open(
|
||||
io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))),
|
||||
WithDecompressors(controller),
|
||||
@@ -831,7 +801,7 @@ func checkStargzTOC(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgst
|
||||
// checkVerifyTOC checks the verification works for the TOC JSON of the passed
|
||||
// stargz. It walks all entries in the stargz and checks the verifications for
|
||||
// all chunks work.
|
||||
func checkVerifyTOC(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
|
||||
func checkVerifyTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
|
||||
sgz, err := Open(
|
||||
io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))),
|
||||
WithDecompressors(controller),
|
||||
@@ -912,9 +882,9 @@ func checkVerifyTOC(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgst
|
||||
// checkVerifyInvalidTOCEntryFail checks if misconfigured TOC JSON can be
|
||||
// detected during the verification and the verification returns an error.
|
||||
func checkVerifyInvalidTOCEntryFail(filename string) check {
|
||||
return func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
|
||||
return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
|
||||
funcs := map[string]rewriteFunc{
|
||||
"lost digest in a entry": func(t TestingT, toc *JTOC, sgz *io.SectionReader) {
|
||||
"lost digest in a entry": func(t *testing.T, toc *JTOC, sgz *io.SectionReader) {
|
||||
var found bool
|
||||
for _, e := range toc.Entries {
|
||||
if cleanEntryName(e.Name) == filename {
|
||||
@@ -932,7 +902,7 @@ func checkVerifyInvalidTOCEntryFail(filename string) check {
|
||||
t.Fatalf("rewrite target not found")
|
||||
}
|
||||
},
|
||||
"duplicated entry offset": func(t TestingT, toc *JTOC, sgz *io.SectionReader) {
|
||||
"duplicated entry offset": func(t *testing.T, toc *JTOC, sgz *io.SectionReader) {
|
||||
var (
|
||||
sampleEntry *TOCEntry
|
||||
targetEntry *TOCEntry
|
||||
@@ -959,7 +929,7 @@ func checkVerifyInvalidTOCEntryFail(filename string) check {
|
||||
}
|
||||
|
||||
for name, rFunc := range funcs {
|
||||
t.Run(name, func(t *TestRunner) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
newSgz, newTocDigest := rewriteTOCJSON(t, io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), rFunc, controller)
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := io.Copy(buf, newSgz); err != nil {
|
||||
@@ -988,7 +958,7 @@ func checkVerifyInvalidTOCEntryFail(filename string) check {
|
||||
// checkVerifyInvalidStargzFail checks if the verification detects that the
|
||||
// given stargz file doesn't match to the expected digest and returns error.
|
||||
func checkVerifyInvalidStargzFail(invalid *io.SectionReader) check {
|
||||
return func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
|
||||
return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
|
||||
cl := newController()
|
||||
rc, err := Build(invalid, WithChunkSize(chunkSize), WithCompression(cl))
|
||||
if err != nil {
|
||||
@@ -1020,7 +990,7 @@ func checkVerifyInvalidStargzFail(invalid *io.SectionReader) check {
|
||||
// checkVerifyBrokenContentFail checks if the verifier detects broken contents
|
||||
// that doesn't match to the expected digest and returns error.
|
||||
func checkVerifyBrokenContentFail(filename string) check {
|
||||
return func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
|
||||
return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
|
||||
// Parse stargz file
|
||||
sgz, err := Open(
|
||||
io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))),
|
||||
@@ -1077,9 +1047,9 @@ func chunkID(name string, offset, size int64) string {
|
||||
return fmt.Sprintf("%s-%d-%d", cleanEntryName(name), offset, size)
|
||||
}
|
||||
|
||||
type rewriteFunc func(t TestingT, toc *JTOC, sgz *io.SectionReader)
|
||||
type rewriteFunc func(t *testing.T, toc *JTOC, sgz *io.SectionReader)
|
||||
|
||||
func rewriteTOCJSON(t TestingT, sgz *io.SectionReader, rewrite rewriteFunc, controller TestingController) (newSgz io.Reader, tocDigest digest.Digest) {
|
||||
func rewriteTOCJSON(t *testing.T, sgz *io.SectionReader, rewrite rewriteFunc, controller TestingController) (newSgz io.Reader, tocDigest digest.Digest) {
|
||||
decodedJTOC, jtocOffset, err := parseStargz(sgz, controller)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to extract TOC JSON: %v", err)
|
||||
@@ -1150,7 +1120,7 @@ func parseStargz(sgz *io.SectionReader, controller TestingController) (decodedJT
|
||||
return decodedJTOC, tocOffset, nil
|
||||
}
|
||||
|
||||
func testWriteAndOpen(t *TestRunner, controllers ...TestingControllerFactory) {
|
||||
func testWriteAndOpen(t *testing.T, controllers ...TestingControllerFactory) {
|
||||
const content = "Some contents"
|
||||
invalidUtf8 := "\xff\xfe\xfd"
|
||||
|
||||
@@ -1494,7 +1464,7 @@ func testWriteAndOpen(t *TestRunner, controllers ...TestingControllerFactory) {
|
||||
for _, srcTarFormat := range []tar.Format{tar.FormatUSTAR, tar.FormatPAX, tar.FormatGNU} {
|
||||
srcTarFormat := srcTarFormat
|
||||
for _, lossless := range []bool{true, false} {
|
||||
t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,lossless=%v,format=%s", newCL(), prefix, lossless, srcTarFormat), func(t *TestRunner) {
|
||||
t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,lossless=%v,format=%s", newCL(), prefix, lossless, srcTarFormat), func(t *testing.T) {
|
||||
var tr io.Reader = buildTar(t, tt.in, prefix, srcTarFormat)
|
||||
origTarDgstr := digest.Canonical.Digester()
|
||||
tr = io.TeeReader(tr, origTarDgstr.Hash())
|
||||
@@ -1560,9 +1530,6 @@ func testWriteAndOpen(t *TestRunner, controllers ...TestingControllerFactory) {
|
||||
if err != nil {
|
||||
t.Fatalf("stargz.Open: %v", err)
|
||||
}
|
||||
if _, ok := r.Lookup(""); !ok {
|
||||
t.Fatalf("failed to lookup rootdir: %v", err)
|
||||
}
|
||||
wantTOCVersion := 1
|
||||
if tt.wantTOCVersion > 0 {
|
||||
wantTOCVersion = tt.wantTOCVersion
|
||||
@@ -1661,7 +1628,7 @@ func digestFor(content string) string {
|
||||
|
||||
type numTOCEntries int
|
||||
|
||||
func (n numTOCEntries) check(t TestingT, r *Reader) {
|
||||
func (n numTOCEntries) check(t *testing.T, r *Reader) {
|
||||
if r.toc == nil {
|
||||
t.Fatal("nil TOC")
|
||||
}
|
||||
@@ -1681,15 +1648,15 @@ func (n numTOCEntries) check(t TestingT, r *Reader) {
|
||||
func checks(s ...stargzCheck) []stargzCheck { return s }
|
||||
|
||||
type stargzCheck interface {
|
||||
check(t TestingT, r *Reader)
|
||||
check(t *testing.T, r *Reader)
|
||||
}
|
||||
|
||||
type stargzCheckFn func(TestingT, *Reader)
|
||||
type stargzCheckFn func(*testing.T, *Reader)
|
||||
|
||||
func (f stargzCheckFn) check(t TestingT, r *Reader) { f(t, r) }
|
||||
func (f stargzCheckFn) check(t *testing.T, r *Reader) { f(t, r) }
|
||||
|
||||
func maxDepth(max int) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
e, ok := r.Lookup("")
|
||||
if !ok {
|
||||
t.Fatal("root directory not found")
|
||||
@@ -1706,7 +1673,7 @@ func maxDepth(max int) stargzCheck {
|
||||
})
|
||||
}
|
||||
|
||||
func getMaxDepth(t TestingT, e *TOCEntry, current, limit int) (max int, rErr error) {
|
||||
func getMaxDepth(t *testing.T, e *TOCEntry, current, limit int) (max int, rErr error) {
|
||||
if current > limit {
|
||||
return -1, fmt.Errorf("walkMaxDepth: exceeds limit: current:%d > limit:%d",
|
||||
current, limit)
|
||||
@@ -1728,7 +1695,7 @@ func getMaxDepth(t TestingT, e *TOCEntry, current, limit int) (max int, rErr err
|
||||
}
|
||||
|
||||
func hasFileLen(file string, wantLen int) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
for _, ent := range r.toc.Entries {
|
||||
if ent.Name == file {
|
||||
if ent.Type != "reg" {
|
||||
@@ -1744,7 +1711,7 @@ func hasFileLen(file string, wantLen int) stargzCheck {
|
||||
}
|
||||
|
||||
func hasFileXattrs(file, name, value string) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
for _, ent := range r.toc.Entries {
|
||||
if ent.Name == file {
|
||||
if ent.Type != "reg" {
|
||||
@@ -1771,7 +1738,7 @@ func hasFileXattrs(file, name, value string) stargzCheck {
|
||||
}
|
||||
|
||||
func hasFileDigest(file string, digest string) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
ent, ok := r.Lookup(file)
|
||||
if !ok {
|
||||
t.Fatalf("didn't find TOCEntry for file %q", file)
|
||||
@@ -1783,7 +1750,7 @@ func hasFileDigest(file string, digest string) stargzCheck {
|
||||
}
|
||||
|
||||
func hasFileContentsWithPreRead(file string, offset int, want string, extra ...chunkInfo) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
extraMap := make(map[string]chunkInfo)
|
||||
for _, e := range extra {
|
||||
extraMap[e.name] = e
|
||||
@@ -1830,7 +1797,7 @@ func hasFileContentsWithPreRead(file string, offset int, want string, extra ...c
|
||||
}
|
||||
|
||||
func hasFileContentsRange(file string, offset int, want string) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
f, err := r.OpenFile(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1847,7 +1814,7 @@ func hasFileContentsRange(file string, offset int, want string) stargzCheck {
|
||||
}
|
||||
|
||||
func hasChunkEntries(file string, wantChunks int) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
ent, ok := r.Lookup(file)
|
||||
if !ok {
|
||||
t.Fatalf("no file for %q", file)
|
||||
@@ -1891,7 +1858,7 @@ func hasChunkEntries(file string, wantChunks int) stargzCheck {
|
||||
}
|
||||
|
||||
func entryHasChildren(dir string, want ...string) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
want := append([]string(nil), want...)
|
||||
var got []string
|
||||
ent, ok := r.Lookup(dir)
|
||||
@@ -1910,7 +1877,7 @@ func entryHasChildren(dir string, want ...string) stargzCheck {
|
||||
}
|
||||
|
||||
func hasDir(file string) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
for _, ent := range r.toc.Entries {
|
||||
if ent.Name == cleanEntryName(file) {
|
||||
if ent.Type != "dir" {
|
||||
@@ -1924,7 +1891,7 @@ func hasDir(file string) stargzCheck {
|
||||
}
|
||||
|
||||
func hasDirLinkCount(file string, count int) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
for _, ent := range r.toc.Entries {
|
||||
if ent.Name == cleanEntryName(file) {
|
||||
if ent.Type != "dir" {
|
||||
@@ -1942,7 +1909,7 @@ func hasDirLinkCount(file string, count int) stargzCheck {
|
||||
}
|
||||
|
||||
func hasMode(file string, mode os.FileMode) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
for _, ent := range r.toc.Entries {
|
||||
if ent.Name == cleanEntryName(file) {
|
||||
if ent.Stat().Mode() != mode {
|
||||
@@ -1957,7 +1924,7 @@ func hasMode(file string, mode os.FileMode) stargzCheck {
|
||||
}
|
||||
|
||||
func hasSymlink(file, target string) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
for _, ent := range r.toc.Entries {
|
||||
if ent.Name == file {
|
||||
if ent.Type != "symlink" {
|
||||
@@ -1973,7 +1940,7 @@ func hasSymlink(file, target string) stargzCheck {
|
||||
}
|
||||
|
||||
func lookupMatch(name string, want *TOCEntry) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
e, ok := r.Lookup(name)
|
||||
if !ok {
|
||||
t.Fatalf("failed to Lookup entry %q", name)
|
||||
@@ -1986,7 +1953,7 @@ func lookupMatch(name string, want *TOCEntry) stargzCheck {
|
||||
}
|
||||
|
||||
func hasEntryOwner(entry string, owner owner) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
ent, ok := r.Lookup(strings.TrimSuffix(entry, "/"))
|
||||
if !ok {
|
||||
t.Errorf("entry %q not found", entry)
|
||||
@@ -2000,7 +1967,7 @@ func hasEntryOwner(entry string, owner owner) stargzCheck {
|
||||
}
|
||||
|
||||
func mustSameEntry(files ...string) stargzCheck {
|
||||
return stargzCheckFn(func(t TestingT, r *Reader) {
|
||||
return stargzCheckFn(func(t *testing.T, r *Reader) {
|
||||
var first *TOCEntry
|
||||
for _, f := range files {
|
||||
if first == nil {
|
||||
@@ -2072,7 +2039,7 @@ func (f tarEntryFunc) appendTar(tw *tar.Writer, prefix string, format tar.Format
|
||||
return f(tw, prefix, format)
|
||||
}
|
||||
|
||||
func buildTar(t TestingT, ents []tarEntry, prefix string, opts ...interface{}) *io.SectionReader {
|
||||
func buildTar(t *testing.T, ents []tarEntry, prefix string, opts ...interface{}) *io.SectionReader {
|
||||
format := tar.FormatUnknown
|
||||
for _, opt := range opts {
|
||||
switch v := opt.(type) {
|
||||
@@ -2281,7 +2248,7 @@ func noPrefetchLandmark() tarEntry {
|
||||
})
|
||||
}
|
||||
|
||||
func regDigest(t TestingT, name string, contentStr string, digestMap map[string]digest.Digest) tarEntry {
|
||||
func regDigest(t *testing.T, name string, contentStr string, digestMap map[string]digest.Digest) tarEntry {
|
||||
if digestMap == nil {
|
||||
t.Fatalf("digest map mustn't be nil")
|
||||
}
|
||||
@@ -2351,7 +2318,7 @@ func (f fileInfoOnlyMode) ModTime() time.Time { return time.Now() }
|
||||
func (f fileInfoOnlyMode) IsDir() bool { return os.FileMode(f).IsDir() }
|
||||
func (f fileInfoOnlyMode) Sys() interface{} { return nil }
|
||||
|
||||
func CheckGzipHasStreams(t TestingT, b []byte, streams []int64) {
|
||||
func CheckGzipHasStreams(t *testing.T, b []byte, streams []int64) {
|
||||
if len(streams) == 0 {
|
||||
return // nop
|
||||
}
|
||||
@@ -2389,7 +2356,7 @@ func CheckGzipHasStreams(t TestingT, b []byte, streams []int64) {
|
||||
}
|
||||
}
|
||||
|
||||
func GzipDiffIDOf(t TestingT, b []byte) string {
|
||||
func GzipDiffIDOf(t *testing.T, b []byte) string {
|
||||
h := sha256.New()
|
||||
zr, err := gzip.NewReader(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
|
||||
10
vendor/github.com/coreos/go-oidc/v3/oidc/jwks.go
generated
vendored
10
vendor/github.com/coreos/go-oidc/v3/oidc/jwks.go
generated
vendored
@@ -11,6 +11,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
jose "github.com/go-jose/go-jose/v4"
|
||||
)
|
||||
@@ -56,12 +57,16 @@ func (s *StaticKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte,
|
||||
// The returned KeySet is a long lived verifier that caches keys based on any
|
||||
// keys change. Reuse a common remote key set instead of creating new ones as needed.
|
||||
func NewRemoteKeySet(ctx context.Context, jwksURL string) *RemoteKeySet {
|
||||
return newRemoteKeySet(ctx, jwksURL)
|
||||
return newRemoteKeySet(ctx, jwksURL, time.Now)
|
||||
}
|
||||
|
||||
func newRemoteKeySet(ctx context.Context, jwksURL string) *RemoteKeySet {
|
||||
func newRemoteKeySet(ctx context.Context, jwksURL string, now func() time.Time) *RemoteKeySet {
|
||||
if now == nil {
|
||||
now = time.Now
|
||||
}
|
||||
return &RemoteKeySet{
|
||||
jwksURL: jwksURL,
|
||||
now: now,
|
||||
// For historical reasons, this package uses contexts for configuration, not just
|
||||
// cancellation. In hindsight, this was a bad idea.
|
||||
//
|
||||
@@ -76,6 +81,7 @@ func newRemoteKeySet(ctx context.Context, jwksURL string) *RemoteKeySet {
|
||||
// a jwks_uri endpoint.
|
||||
type RemoteKeySet struct {
|
||||
jwksURL string
|
||||
now func() time.Time
|
||||
|
||||
// Used for configuration. Cancelation is ignored.
|
||||
ctx context.Context
|
||||
|
||||
99
vendor/github.com/coreos/go-oidc/v3/oidc/verify.go
generated
vendored
99
vendor/github.com/coreos/go-oidc/v3/oidc/verify.go
generated
vendored
@@ -1,11 +1,15 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jose "github.com/go-jose/go-jose/v4"
|
||||
@@ -141,6 +145,18 @@ func (p *Provider) newVerifier(keySet KeySet, config *Config) *IDTokenVerifier {
|
||||
return NewVerifier(p.issuer, keySet, config)
|
||||
}
|
||||
|
||||
func parseJWT(p string) ([]byte, error) {
|
||||
parts := strings.Split(p, ".")
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts))
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err)
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func contains(sli []string, ele string) bool {
|
||||
for _, s := range sli {
|
||||
if s == ele {
|
||||
@@ -203,49 +219,12 @@ func resolveDistributedClaim(ctx context.Context, verifier *IDTokenVerifier, src
|
||||
//
|
||||
// token, err := verifier.Verify(ctx, rawIDToken)
|
||||
func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDToken, error) {
|
||||
var supportedSigAlgs []jose.SignatureAlgorithm
|
||||
for _, alg := range v.config.SupportedSigningAlgs {
|
||||
supportedSigAlgs = append(supportedSigAlgs, jose.SignatureAlgorithm(alg))
|
||||
}
|
||||
if len(supportedSigAlgs) == 0 {
|
||||
// If no algorithms were specified by both the config and discovery, default
|
||||
// to the one mandatory algorithm "RS256".
|
||||
supportedSigAlgs = []jose.SignatureAlgorithm{jose.RS256}
|
||||
}
|
||||
if v.config.InsecureSkipSignatureCheck {
|
||||
// "none" is a required value to even parse a JWT with the "none" algorithm
|
||||
// using go-jose.
|
||||
supportedSigAlgs = append(supportedSigAlgs, "none")
|
||||
}
|
||||
|
||||
// Parse and verify the signature first. This at least forces the user to have
|
||||
// a valid, signed ID token before we do any other processing.
|
||||
jws, err := jose.ParseSigned(rawIDToken, supportedSigAlgs)
|
||||
// Throw out tokens with invalid claims before trying to verify the token. This lets
|
||||
// us do cheap checks before possibly re-syncing keys.
|
||||
payload, err := parseJWT(rawIDToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
|
||||
}
|
||||
switch len(jws.Signatures) {
|
||||
case 0:
|
||||
return nil, fmt.Errorf("oidc: id token not signed")
|
||||
case 1:
|
||||
default:
|
||||
return nil, fmt.Errorf("oidc: multiple signatures on id token not supported")
|
||||
}
|
||||
sig := jws.Signatures[0]
|
||||
|
||||
var payload []byte
|
||||
if v.config.InsecureSkipSignatureCheck {
|
||||
// Yolo mode.
|
||||
payload = jws.UnsafePayloadWithoutVerification()
|
||||
} else {
|
||||
// The JWT is attached here for the happy path to avoid the verifier from
|
||||
// having to parse the JWT twice.
|
||||
ctx = context.WithValue(ctx, parsedJWTKey, jws)
|
||||
payload, err = v.keySet.VerifySignature(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to verify signature: %v", err)
|
||||
}
|
||||
}
|
||||
var token idToken
|
||||
if err := json.Unmarshal(payload, &token); err != nil {
|
||||
return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
|
||||
@@ -275,7 +254,6 @@ func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDTok
|
||||
AccessTokenHash: token.AtHash,
|
||||
claims: payload,
|
||||
distributedClaims: distributedClaims,
|
||||
sigAlgorithm: sig.Header.Algorithm,
|
||||
}
|
||||
|
||||
// Check issuer.
|
||||
@@ -328,6 +306,45 @@ func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDTok
|
||||
}
|
||||
}
|
||||
|
||||
if v.config.InsecureSkipSignatureCheck {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
var supportedSigAlgs []jose.SignatureAlgorithm
|
||||
for _, alg := range v.config.SupportedSigningAlgs {
|
||||
supportedSigAlgs = append(supportedSigAlgs, jose.SignatureAlgorithm(alg))
|
||||
}
|
||||
if len(supportedSigAlgs) == 0 {
|
||||
// If no algorithms were specified by both the config and discovery, default
|
||||
// to the one mandatory algorithm "RS256".
|
||||
supportedSigAlgs = []jose.SignatureAlgorithm{jose.RS256}
|
||||
}
|
||||
jws, err := jose.ParseSigned(rawIDToken, supportedSigAlgs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
|
||||
}
|
||||
|
||||
switch len(jws.Signatures) {
|
||||
case 0:
|
||||
return nil, fmt.Errorf("oidc: id token not signed")
|
||||
case 1:
|
||||
default:
|
||||
return nil, fmt.Errorf("oidc: multiple signatures on id token not supported")
|
||||
}
|
||||
sig := jws.Signatures[0]
|
||||
t.sigAlgorithm = sig.Header.Algorithm
|
||||
|
||||
ctx = context.WithValue(ctx, parsedJWTKey, jws)
|
||||
gotPayload, err := v.keySet.VerifySignature(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to verify signature: %v", err)
|
||||
}
|
||||
|
||||
// Ensure that the payload returned by the square actually matches the payload parsed earlier.
|
||||
if !bytes.Equal(gotPayload, payload) {
|
||||
return nil, errors.New("oidc: internal error, payload parsed did not match previous payload")
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
|
||||
60
vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
generated
vendored
60
vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
generated
vendored
@@ -1,60 +0,0 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
# Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
# Copyright (C) 2025 SUSE LLC
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
version: "2"
|
||||
|
||||
run:
|
||||
build-tags:
|
||||
- libpathrs
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- containedctx
|
||||
- contextcheck
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- forcetypeassert
|
||||
- godot
|
||||
- goprintffuncname
|
||||
- govet
|
||||
- importas
|
||||
- ineffassign
|
||||
- makezero
|
||||
- misspell
|
||||
- musttag
|
||||
- nilerr
|
||||
- nilnesserr
|
||||
- nilnil
|
||||
- noctx
|
||||
- prealloc
|
||||
- revive
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- usetesting
|
||||
settings:
|
||||
govet:
|
||||
enable:
|
||||
- nilness
|
||||
testifylint:
|
||||
enable-all: true
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
settings:
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/cyphar/filepath-securejoin
|
||||
201
vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
generated
vendored
201
vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
generated
vendored
@@ -6,198 +6,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [Unreleased] ##
|
||||
|
||||
## [0.6.1] - 2025-11-19 ##
|
||||
|
||||
> At last up jumped the cunning spider, and fiercely held her fast.
|
||||
|
||||
### Fixed ###
|
||||
- Our logic for deciding whether to use `openat2(2)` or fallback to an `O_PATH`
|
||||
resolver would cache the result to avoid doing needless test runs of
|
||||
`openat2(2)`. However, this causes issues when `pathrs-lite` is being used by
|
||||
a program that applies new seccomp-bpf filters onto itself -- if the filter
|
||||
denies `openat2(2)` then we would return that error rather than falling back
|
||||
to the `O_PATH` resolver. To resolve this issue, we no longer cache the
|
||||
result if `openat2(2)` was successful, only if there was an error.
|
||||
- A file descriptor leak in our `openat2` wrapper (when doing the necessary
|
||||
`dup` for `RESOLVE_IN_ROOT`) has been removed.
|
||||
|
||||
## [0.5.2] - 2025-11-19 ##
|
||||
|
||||
> "Will you walk into my parlour?" said a spider to a fly.
|
||||
|
||||
### Fixed ###
|
||||
- Our logic for deciding whether to use `openat2(2)` or fallback to an `O_PATH`
|
||||
resolver would cache the result to avoid doing needless test runs of
|
||||
`openat2(2)`. However, this causes issues when `pathrs-lite` is being used by
|
||||
a program that applies new seccomp-bpf filters onto itself -- if the filter
|
||||
denies `openat2(2)` then we would return that error rather than falling back
|
||||
to the `O_PATH` resolver. To resolve this issue, we no longer cache the
|
||||
result if `openat2(2)` was successful, only if there was an error.
|
||||
- A file descriptor leak in our `openat2` wrapper (when doing the necessary
|
||||
`dup` for `RESOLVE_IN_ROOT`) has been removed.
|
||||
|
||||
## [0.6.0] - 2025-11-03 ##
|
||||
|
||||
> By the Power of Greyskull!
|
||||
|
||||
### Breaking ###
|
||||
- The deprecated `MkdirAll`, `MkdirAllHandle`, `OpenInRoot`, `OpenatInRoot` and
|
||||
`Reopen` wrappers have been removed. Please switch to using `pathrs-lite`
|
||||
directly.
|
||||
|
||||
### Added ###
|
||||
- `pathrs-lite` now has support for using libpathrs as a backend. This is
|
||||
opt-in and can be enabled at build time with the `libpathrs` build tag. The
|
||||
intention is to allow for downstream libraries and other projects to make use
|
||||
of the pure-Go `github.com/cyphar/filepath-securejoin/pathrs-lite` package
|
||||
and distributors can then opt-in to using `libpathrs` for the entire binary
|
||||
if they wish.
|
||||
|
||||
## [0.5.1] - 2025-10-31 ##
|
||||
|
||||
> Spooky scary skeletons send shivers down your spine!
|
||||
|
||||
### Changed ###
|
||||
- `openat2` can return `-EAGAIN` if it detects a possible attack in certain
|
||||
scenarios (namely if there was a rename or mount while walking a path with a
|
||||
`..` component). While this is necessary to avoid a denial-of-service in the
|
||||
kernel, it does require retry loops in userspace.
|
||||
|
||||
In previous versions, `pathrs-lite` would retry `openat2` 32 times before
|
||||
returning an error, but we've received user reports that this limit can be
|
||||
hit on systems with very heavy load. In some synthetic benchmarks (testing
|
||||
the worst-case of an attacker doing renames in a tight loop on every core of
|
||||
a 16-core machine) we managed to get a ~3% failure rate in runc. We have
|
||||
improved this situation in two ways:
|
||||
|
||||
* We have now increased this limit to 128, which should be good enough for
|
||||
most use-cases without becoming a denial-of-service vector (the number of
|
||||
syscalls called by the `O_PATH` resolver in a typical case is within the
|
||||
same ballpark). The same benchmarks show a failure rate of ~0.12% which
|
||||
(while not zero) is probably sufficient for most users.
|
||||
|
||||
* In addition, we now return a `unix.EAGAIN` error that is bubbled up and can
|
||||
be detected by callers. This means that callers with stricter requirements
|
||||
to avoid spurious errors can choose to do their own infinite `EAGAIN` retry
|
||||
loop (though we would strongly recommend users use time-based deadlines in
|
||||
such retry loops to avoid potentially unbounded denials-of-service).
|
||||
|
||||
## [0.5.0] - 2025-09-26 ##
|
||||
|
||||
> Let the past die. Kill it if you have to.
|
||||
|
||||
> **NOTE**: With this release, some parts of
|
||||
> `github.com/cyphar/filepath-securejoin` are now licensed under the Mozilla
|
||||
> Public License (version 2). Please see [COPYING.md][] as well as the the
|
||||
> license header in each file for more details.
|
||||
|
||||
[COPYING.md]: ./COPYING.md
|
||||
|
||||
### Breaking ###
|
||||
- The new API introduced in the [0.3.0][] release has been moved to a new
|
||||
subpackage called `pathrs-lite`. This was primarily done to better indicate
|
||||
the split between the new and old APIs, as well as indicate to users the
|
||||
purpose of this subpackage (it is a less complete version of [libpathrs][]).
|
||||
|
||||
We have added some wrappers to the top-level package to ease the transition,
|
||||
but those are deprecated and will be removed in the next minor release of
|
||||
filepath-securejoin. Users should update their import paths.
|
||||
|
||||
This new subpackage has also been relicensed under the Mozilla Public License
|
||||
(version 2), please see [COPYING.md][] for more details.
|
||||
|
||||
### Added ###
|
||||
- Most of the key bits the safe `procfs` API have now been exported and are
|
||||
available in `github.com/cyphar/filepath-securejoin/pathrs-lite/procfs`. At
|
||||
the moment this primarily consists of a new `procfs.Handle` API:
|
||||
|
||||
* `OpenProcRoot` returns a new handle to `/proc`, endeavouring to make it
|
||||
safe if possible (`subset=pid` to protect against mistaken write attacks
|
||||
and leaks, as well as using `fsopen(2)` to avoid racing mount attacks).
|
||||
|
||||
`OpenUnsafeProcRoot` returns a handle without attempting to create one
|
||||
with `subset=pid`, which makes it more dangerous to leak. Most users
|
||||
should use `OpenProcRoot` (even if you need to use `ProcRoot` as the base
|
||||
of an operation, as filepath-securejoin will internally open a handle when
|
||||
necessary).
|
||||
|
||||
* The `(*procfs.Handle).Open*` family of methods lets you get a safe
|
||||
`O_PATH` handle to subpaths within `/proc` for certain subpaths.
|
||||
|
||||
For `OpenThreadSelf`, the returned `ProcThreadSelfCloser` needs to be
|
||||
called after you completely finish using the handle (this is necessary
|
||||
because Go is multi-threaded and `ProcThreadSelf` references
|
||||
`/proc/thread-self` which may disappear if we do not
|
||||
`runtime.LockOSThread` -- `ProcThreadSelfCloser` is currently equivalent
|
||||
to `runtime.UnlockOSThread`).
|
||||
|
||||
Note that you cannot open any `procfs` symlinks (most notably magic-links)
|
||||
using this API. At the moment, filepath-securejoin does not support this
|
||||
feature (but [libpathrs][] does).
|
||||
|
||||
* `ProcSelfFdReadlink` lets you get the in-kernel path representation of a
|
||||
file descriptor (think `readlink("/proc/self/fd/...")`), except that we
|
||||
verify that there aren't any tricky overmounts that could fool the
|
||||
process.
|
||||
|
||||
Please be aware that the returned string is simply a snapshot at that
|
||||
particular moment, and an attacker could move the file being pointed to.
|
||||
In addition, complex namespace configurations could result in non-sensical
|
||||
or confusing paths to be returned. The value received from this function
|
||||
should only be used as secondary verification of some security property,
|
||||
not as proof that a particular handle has a particular path.
|
||||
|
||||
The procfs handle used internally by the API is the same as the rest of
|
||||
`filepath-securejoin` (for privileged programs this is usually a private
|
||||
in-process `procfs` instance created with `fsopen(2)`).
|
||||
|
||||
As before, this is intended as a stop-gap before users migrate to
|
||||
[libpathrs][], which provides a far more extensive safe `procfs` API and is
|
||||
generally more robust.
|
||||
|
||||
- Previously, the hardened procfs implementation (used internally within
|
||||
`Reopen` and `Open(at)InRoot`) only protected against overmount attacks on
|
||||
systems with `openat2(2)` (Linux 5.6) or systems with `fsopen(2)` or
|
||||
`open_tree(2)` (Linux 5.2) and programs with privileges to use them (with
|
||||
some caveats about locked mounts that probably affect very few users). For
|
||||
other users, an attacker with the ability to create malicious mounts (on most
|
||||
systems, a sysadmin) could trick you into operating on files you didn't
|
||||
expect. This attack only really makes sense in the context of container
|
||||
runtime implementations.
|
||||
|
||||
This was considered a reasonable trade-off, as the long-term intention was to
|
||||
get all users to just switch to [libpathrs][] if they wanted to use the safe
|
||||
`procfs` API (which had more extensive protections, and is what these new
|
||||
protections in `filepath-securejoin` are based on). However, as the API
|
||||
is now being exported it seems unwise to advertise the API as "safe" if we do
|
||||
not protect against known attacks.
|
||||
|
||||
The procfs API is now more protected against attackers on systems lacking the
|
||||
aforementioned protections. However, the most comprehensive of these
|
||||
protections effectively rely on [`statx(STATX_MNT_ID)`][statx.2] (Linux 5.8).
|
||||
On older kernel versions, there is no effective protection (there is some
|
||||
minimal protection against non-`procfs` filesystem components but a
|
||||
sufficiently clever attacker can work around those). In addition,
|
||||
`STATX_MNT_ID` is vulnerable to mount ID reuse attacks by sufficiently
|
||||
motivated and privileged attackers -- this problem is mitigated with
|
||||
`STATX_MNT_ID_UNIQUE` (Linux 6.8) but that raises the minimum kernel version
|
||||
for more protection.
|
||||
|
||||
The fact that these protections are quite limited despite needing a fair bit
|
||||
of extra code to handle was one of the primary reasons we did not initially
|
||||
implement this in `filepath-securejoin` ([libpathrs][] supports all of this,
|
||||
of course).
|
||||
|
||||
### Fixed ###
|
||||
- RHEL 8 kernels have backports of `fsopen(2)` but in some testing we've found
|
||||
that it has very bad (and very difficult to debug) performance issues, and so
|
||||
we will explicitly refuse to use `fsopen(2)` if the running kernel version is
|
||||
pre-5.2 and will instead fallback to `open("/proc")`.
|
||||
|
||||
[CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv
|
||||
[libpathrs]: https://github.com/cyphar/libpathrs
|
||||
[statx.2]: https://www.man7.org/linux/man-pages/man2/statx.2.html
|
||||
|
||||
## [0.4.1] - 2025-01-28 ##
|
||||
|
||||
### Fixed ###
|
||||
@@ -365,7 +173,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
safe to start migrating to as we have extensive tests ensuring they behave
|
||||
correctly and are safe against various races and other attacks.
|
||||
|
||||
[libpathrs]: https://github.com/cyphar/libpathrs
|
||||
[libpathrs]: https://github.com/openSUSE/libpathrs
|
||||
[open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html
|
||||
|
||||
## [0.2.5] - 2024-05-03 ##
|
||||
@@ -430,12 +238,7 @@ This is our first release of `github.com/cyphar/filepath-securejoin`,
|
||||
containing a full implementation with a coverage of 93.5% (the only missing
|
||||
cases are the error cases, which are hard to mocktest at the moment).
|
||||
|
||||
[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.6.1...HEAD
|
||||
[0.6.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.6.0...v0.6.1
|
||||
[0.6.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...v0.6.0
|
||||
[0.5.2]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.1...v0.5.2
|
||||
[0.5.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...v0.5.1
|
||||
[0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0
|
||||
[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...HEAD
|
||||
[0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1
|
||||
[0.4.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.6...v0.4.0
|
||||
[0.3.6]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.5...v0.3.6
|
||||
|
||||
447
vendor/github.com/cyphar/filepath-securejoin/COPYING.md
generated
vendored
447
vendor/github.com/cyphar/filepath-securejoin/COPYING.md
generated
vendored
@@ -1,447 +0,0 @@
|
||||
## COPYING ##
|
||||
|
||||
`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0`
|
||||
|
||||
This project is made up of code licensed under different licenses. Which code
|
||||
you use will have an impact on whether only one or both licenses apply to your
|
||||
usage of this library.
|
||||
|
||||
Note that **each file** in this project individually has a code comment at the
|
||||
start describing the license of that particular file -- this is the most
|
||||
accurate license information of this project; in case there is any conflict
|
||||
between this document and the comment at the start of a file, the comment shall
|
||||
take precedence. The only purpose of this document is to work around [a known
|
||||
technical limitation of pkg.go.dev's license checking tool when dealing with
|
||||
non-trivial project licenses][go75067].
|
||||
|
||||
[go75067]: https://go.dev/issue/75067
|
||||
|
||||
### `BSD-3-Clause` ###
|
||||
|
||||
At time of writing, the following files and directories are licensed under the
|
||||
BSD-3-Clause license:
|
||||
|
||||
* `doc.go`
|
||||
* `join*.go`
|
||||
* `vfs.go`
|
||||
* `internal/consts/*.go`
|
||||
* `pathrs-lite/internal/gocompat/*.go`
|
||||
* `pathrs-lite/internal/kernelversion/*.go`
|
||||
|
||||
The text of the BSD-3-Clause license used by this project is the following (the
|
||||
text is also available from the [`LICENSE.BSD`](./LICENSE.BSD) file):
|
||||
|
||||
```
|
||||
Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||
Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
|
||||
### `MPL-2.0` ###
|
||||
|
||||
All other files (unless otherwise marked) are licensed under the Mozilla Public
|
||||
License (version 2.0).
|
||||
|
||||
The text of the Mozilla Public License (version 2.0) is the following (the text
|
||||
is also available from the [`LICENSE.MPL-2.0`](./LICENSE.MPL-2.0) file):
|
||||
|
||||
```
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
```
|
||||
373
vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
generated
vendored
373
vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
generated
vendored
@@ -1,373 +0,0 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
21
vendor/github.com/cyphar/filepath-securejoin/README.md
generated
vendored
21
vendor/github.com/cyphar/filepath-securejoin/README.md
generated
vendored
@@ -67,8 +67,7 @@ func SecureJoin(root, unsafePath string) (string, error) {
|
||||
[libpathrs]: https://github.com/openSUSE/libpathrs
|
||||
[go#20126]: https://github.com/golang/go/issues/20126
|
||||
|
||||
### <a name="new-api" /> New API ###
|
||||
[#new-api]: #new-api
|
||||
### New API ###
|
||||
|
||||
While we recommend users switch to [libpathrs][libpathrs] as soon as it has a
|
||||
stable release, some methods implemented by libpathrs have been ported to this
|
||||
@@ -166,19 +165,5 @@ after `MkdirAll`).
|
||||
|
||||
### License ###
|
||||
|
||||
`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0`
|
||||
|
||||
Some of the code in this project is derived from Go, and is licensed under a
|
||||
BSD 3-clause license (available in `LICENSE.BSD`). Other files (many of which
|
||||
are derived from [libpathrs][libpathrs]) are licensed under the Mozilla Public
|
||||
License version 2.0 (available in `LICENSE.MPL-2.0`). If you are using the
|
||||
["New API" described above][#new-api], you are probably using code from files
|
||||
released under this license.
|
||||
|
||||
Every source file in this project has a copyright header describing its
|
||||
license. Please check the license headers of each file to see what license
|
||||
applies to it.
|
||||
|
||||
See [COPYING.md](./COPYING.md) for some more details.
|
||||
|
||||
[umoci]: https://github.com/opencontainers/umoci
|
||||
The license of this project is the same as Go, which is a BSD 3-clause license
|
||||
available in the `LICENSE` file.
|
||||
|
||||
2
vendor/github.com/cyphar/filepath-securejoin/VERSION
generated
vendored
2
vendor/github.com/cyphar/filepath-securejoin/VERSION
generated
vendored
@@ -1 +1 @@
|
||||
0.6.1
|
||||
0.4.1
|
||||
|
||||
29
vendor/github.com/cyphar/filepath-securejoin/codecov.yml
generated
vendored
29
vendor/github.com/cyphar/filepath-securejoin/codecov.yml
generated
vendored
@@ -1,29 +0,0 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
# Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
# Copyright (C) 2025 SUSE LLC
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
comment:
|
||||
layout: "condensed_header, reach, diff, components, condensed_files, condensed_footer"
|
||||
require_changes: true
|
||||
branches:
|
||||
- main
|
||||
|
||||
coverage:
|
||||
range: 60..100
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 85%
|
||||
threshold: 0%
|
||||
patch:
|
||||
default:
|
||||
target: auto
|
||||
informational: true
|
||||
|
||||
github_checks:
|
||||
annotations: false
|
||||
34
vendor/github.com/cyphar/filepath-securejoin/doc.go
generated
vendored
34
vendor/github.com/cyphar/filepath-securejoin/doc.go
generated
vendored
@@ -1,5 +1,3 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||
// Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@@ -16,13 +14,14 @@
|
||||
// **not** safe against race conditions where an attacker changes the
|
||||
// filesystem after (or during) the [SecureJoin] operation.
|
||||
//
|
||||
// The new API is available in the [pathrs-lite] subpackage, and provide
|
||||
// protections against racing attackers as well as several other key
|
||||
// protections against attacks often seen by container runtimes. As the name
|
||||
// suggests, [pathrs-lite] is a stripped down (pure Go) reimplementation of
|
||||
// [libpathrs]. The main APIs provided are [OpenInRoot], [MkdirAll], and
|
||||
// [procfs.Handle] -- other APIs are not planned to be ported. The long-term
|
||||
// goal is for users to migrate to [libpathrs] which is more fully-featured.
|
||||
// The new API is made up of [OpenInRoot] and [MkdirAll] (and derived
|
||||
// functions). These are safe against racing attackers and have several other
|
||||
// protections that are not provided by the legacy API. There are many more
|
||||
// operations that most programs expect to be able to do safely, but we do not
|
||||
// provide explicit support for them because we want to encourage users to
|
||||
// switch to [libpathrs](https://github.com/openSUSE/libpathrs) which is a
|
||||
// cross-language next-generation library that is entirely designed around
|
||||
// operating on paths safely.
|
||||
//
|
||||
// securejoin has been used by several container runtimes (Docker, runc,
|
||||
// Kubernetes, etc) for quite a few years as a de-facto standard for operating
|
||||
@@ -32,16 +31,9 @@
|
||||
// API as soon as possible (or even better, switch to libpathrs).
|
||||
//
|
||||
// This project was initially intended to be included in the Go standard
|
||||
// library, but it was rejected (see https://go.dev/issue/20126). Much later,
|
||||
// [os.Root] was added to the Go stdlib that shares some of the goals of
|
||||
// filepath-securejoin. However, its design is intended to work like
|
||||
// openat2(RESOLVE_BENEATH) which does not fit the usecase of container
|
||||
// runtimes and most system tools.
|
||||
//
|
||||
// [pathrs-lite]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite
|
||||
// [libpathrs]: https://github.com/openSUSE/libpathrs
|
||||
// [OpenInRoot]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#OpenInRoot
|
||||
// [MkdirAll]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite#MkdirAll
|
||||
// [procfs.Handle]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin/pathrs-lite/procfs#Handle
|
||||
// [os.Root]: https:///pkg.go.dev/os#Root
|
||||
// library, but [it was rejected](https://go.dev/issue/20126). There is now a
|
||||
// [new Go proposal](https://go.dev/issue/67002) for a safe path resolution API
|
||||
// that shares some of the goals of filepath-securejoin. However, that design
|
||||
// is intended to work like `openat2(RESOLVE_BENEATH)` which does not fit the
|
||||
// usecase of container runtimes and most system tools.
|
||||
package securejoin
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux && go1.20
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gocompat
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except
|
||||
// wrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except
|
||||
// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap)
|
||||
// is only guaranteed to give you baseErr.
|
||||
func WrapBaseError(baseErr, extraErr error) error {
|
||||
func wrapBaseError(baseErr, extraErr error) error {
|
||||
return fmt.Errorf("%w: %w", extraErr, baseErr)
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !go1.20
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gocompat
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -29,10 +27,10 @@ func (err wrappedError) Error() string {
|
||||
return fmt.Sprintf("%v: %v", err.isError, err.inner)
|
||||
}
|
||||
|
||||
// WrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except
|
||||
// wrapBaseError is a helper that is equivalent to fmt.Errorf("%w: %w"), except
|
||||
// that on pre-1.20 Go versions only errors.Is() works properly (errors.Unwrap)
|
||||
// is only guaranteed to give you baseErr.
|
||||
func WrapBaseError(baseErr, extraErr error) error {
|
||||
func wrapBaseError(baseErr, extraErr error) error {
|
||||
return wrappedError{
|
||||
inner: baseErr,
|
||||
isError: extraErr,
|
||||
32
vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go
generated
vendored
Normal file
32
vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_go121.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
//go:build linux && go1.21
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func slices_DeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S {
|
||||
return slices.DeleteFunc(slice, delFn)
|
||||
}
|
||||
|
||||
func slices_Contains[S ~[]E, E comparable](slice S, val E) bool {
|
||||
return slices.Contains(slice, val)
|
||||
}
|
||||
|
||||
func slices_Clone[S ~[]E, E any](slice S) S {
|
||||
return slices.Clone(slice)
|
||||
}
|
||||
|
||||
func sync_OnceValue[T any](f func() T) func() T {
|
||||
return sync.OnceValue(f)
|
||||
}
|
||||
|
||||
func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
||||
return sync.OnceValues(f)
|
||||
}
|
||||
124
vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go
generated
vendored
Normal file
124
vendor/github.com/cyphar/filepath-securejoin/gocompat_generics_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
//go:build linux && !go1.21
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// These are very minimal implementations of functions that appear in Go 1.21's
|
||||
// stdlib, included so that we can build on older Go versions. Most are
|
||||
// borrowed directly from the stdlib, and a few are modified to be "obviously
|
||||
// correct" without needing to copy too many other helpers.
|
||||
|
||||
// clearSlice is equivalent to the builtin clear from Go 1.21.
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func clearSlice[S ~[]E, E any](slice S) {
|
||||
var zero E
|
||||
for i := range slice {
|
||||
slice[i] = zero
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func slices_IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
|
||||
for i := range s {
|
||||
if f(s[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func slices_DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
|
||||
i := slices_IndexFunc(s, del)
|
||||
if i == -1 {
|
||||
return s
|
||||
}
|
||||
// Don't start copying elements until we find one to delete.
|
||||
for j := i + 1; j < len(s); j++ {
|
||||
if v := s[j]; !del(v) {
|
||||
s[i] = v
|
||||
i++
|
||||
}
|
||||
}
|
||||
clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
|
||||
return s[:i]
|
||||
}
|
||||
|
||||
// Similar to the stdlib slices.Contains, except that we don't have
|
||||
// slices.Index so we need to use slices.IndexFunc for this non-Func helper.
|
||||
func slices_Contains[S ~[]E, E comparable](s S, v E) bool {
|
||||
return slices_IndexFunc(s, func(e E) bool { return e == v }) >= 0
|
||||
}
|
||||
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func slices_Clone[S ~[]E, E any](s S) S {
|
||||
// Preserve nil in case it matters.
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return append(S([]E{}), s...)
|
||||
}
|
||||
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func sync_OnceValue[T any](f func() T) func() T {
|
||||
var (
|
||||
once sync.Once
|
||||
valid bool
|
||||
p any
|
||||
result T
|
||||
)
|
||||
g := func() {
|
||||
defer func() {
|
||||
p = recover()
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
result = f()
|
||||
f = nil
|
||||
valid = true
|
||||
}
|
||||
return func() T {
|
||||
once.Do(g)
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func sync_OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
||||
var (
|
||||
once sync.Once
|
||||
valid bool
|
||||
p any
|
||||
r1 T1
|
||||
r2 T2
|
||||
)
|
||||
g := func() {
|
||||
defer func() {
|
||||
p = recover()
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
r1, r2 = f()
|
||||
f = nil
|
||||
valid = true
|
||||
}
|
||||
return func() (T1, T2) {
|
||||
once.Do(g)
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
return r1, r2
|
||||
}
|
||||
}
|
||||
15
vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go
generated
vendored
15
vendor/github.com/cyphar/filepath-securejoin/internal/consts/consts.go
generated
vendored
@@ -1,15 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||
// Copyright (C) 2017-2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package consts contains the definitions of internal constants used
|
||||
// throughout filepath-securejoin.
|
||||
package consts
|
||||
|
||||
// MaxSymlinkLimit is the maximum number of symlinks that can be encountered
|
||||
// during a single lookup before returning -ELOOP. At time of writing, Linux
|
||||
// has an internal limit of 40.
|
||||
const MaxSymlinkLimit = 255
|
||||
23
vendor/github.com/cyphar/filepath-securejoin/join.go
generated
vendored
23
vendor/github.com/cyphar/filepath-securejoin/join.go
generated
vendored
@@ -1,5 +1,3 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||
// Copyright (C) 2017-2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
@@ -13,10 +11,10 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/internal/consts"
|
||||
)
|
||||
|
||||
const maxSymlinkLimit = 255
|
||||
|
||||
// IsNotExist tells you if err is an error that implies that either the path
|
||||
// accessed does not exist (or path components don't exist). This is
|
||||
// effectively a more broad version of [os.IsNotExist].
|
||||
@@ -51,13 +49,12 @@ func hasDotDot(path string) bool {
|
||||
return strings.Contains("/"+path+"/", "/../")
|
||||
}
|
||||
|
||||
// SecureJoinVFS joins the two given path components (similar to
|
||||
// [filepath.Join]) except that the returned path is guaranteed to be scoped
|
||||
// inside the provided root path (when evaluated). Any symbolic links in the
|
||||
// path are evaluated with the given root treated as the root of the
|
||||
// filesystem, similar to a chroot. The filesystem state is evaluated through
|
||||
// the given [VFS] interface (if nil, the standard [os].* family of functions
|
||||
// are used).
|
||||
// SecureJoinVFS joins the two given path components (similar to [filepath.Join]) except
|
||||
// that the returned path is guaranteed to be scoped inside the provided root
|
||||
// path (when evaluated). Any symbolic links in the path are evaluated with the
|
||||
// given root treated as the root of the filesystem, similar to a chroot. The
|
||||
// filesystem state is evaluated through the given [VFS] interface (if nil, the
|
||||
// standard [os].* family of functions are used).
|
||||
//
|
||||
// Note that the guarantees provided by this function only apply if the path
|
||||
// components in the returned string are not modified (in other words are not
|
||||
@@ -81,7 +78,7 @@ func hasDotDot(path string) bool {
|
||||
// fully resolved using [filepath.EvalSymlinks] or otherwise constructed to
|
||||
// avoid containing symlink components. Of course, the root also *must not* be
|
||||
// attacker-controlled.
|
||||
func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { //nolint:revive // name is part of public API
|
||||
func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
|
||||
// The root path must not contain ".." components, otherwise when we join
|
||||
// the subpath we will end up with a weird path. We could work around this
|
||||
// in other ways but users shouldn't be giving us non-lexical root paths in
|
||||
@@ -141,7 +138,7 @@ func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { //nolint:
|
||||
// It's a symlink, so get its contents and expand it by prepending it
|
||||
// to the yet-unparsed path.
|
||||
linksWalked++
|
||||
if linksWalked > consts.MaxSymlinkLimit {
|
||||
if linksWalked > maxSymlinkLimit {
|
||||
return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gopathrs
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -20,12 +15,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/internal/consts"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
|
||||
)
|
||||
|
||||
type symlinkStackEntry struct {
|
||||
@@ -123,12 +112,12 @@ func (s *symlinkStack) push(dir *os.File, remainingPath, linkTarget string) erro
|
||||
return nil
|
||||
}
|
||||
// Split the link target and clean up any "" parts.
|
||||
linkTargetParts := gocompat.SlicesDeleteFunc(
|
||||
linkTargetParts := slices_DeleteFunc(
|
||||
strings.Split(linkTarget, "/"),
|
||||
func(part string) bool { return part == "" || part == "." })
|
||||
|
||||
// Copy the directory so the caller doesn't close our copy.
|
||||
dirCopy, err := fd.Dup(dir)
|
||||
dirCopy, err := dupFile(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -166,15 +155,15 @@ func (s *symlinkStack) PopTopSymlink() (*os.File, string, bool) {
|
||||
return tailEntry.dir, tailEntry.remainingPath, true
|
||||
}
|
||||
|
||||
// PartialLookupInRoot tries to lookup as much of the request path as possible
|
||||
// partialLookupInRoot tries to lookup as much of the request path as possible
|
||||
// within the provided root (a-la RESOLVE_IN_ROOT) and opens the final existing
|
||||
// component of the requested path, returning a file handle to the final
|
||||
// existing component and a string containing the remaining path components.
|
||||
func PartialLookupInRoot(root fd.Fd, unsafePath string) (*os.File, string, error) {
|
||||
func partialLookupInRoot(root *os.File, unsafePath string) (*os.File, string, error) {
|
||||
return lookupInRoot(root, unsafePath, true)
|
||||
}
|
||||
|
||||
func completeLookupInRoot(root fd.Fd, unsafePath string) (*os.File, error) {
|
||||
func completeLookupInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
||||
handle, remainingPath, err := lookupInRoot(root, unsafePath, false)
|
||||
if remainingPath != "" && err == nil {
|
||||
// should never happen
|
||||
@@ -185,7 +174,7 @@ func completeLookupInRoot(root fd.Fd, unsafePath string) (*os.File, error) {
|
||||
return handle, err
|
||||
}
|
||||
|
||||
func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) {
|
||||
func lookupInRoot(root *os.File, unsafePath string, partial bool) (Handle *os.File, _ string, _ error) {
|
||||
unsafePath = filepath.ToSlash(unsafePath) // noop
|
||||
|
||||
// This is very similar to SecureJoin, except that we operate on the
|
||||
@@ -193,25 +182,20 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File,
|
||||
// managed open, along with the remaining path components not opened.
|
||||
|
||||
// Try to use openat2 if possible.
|
||||
//
|
||||
// NOTE: If openat2(2) works normally but fails for this lookup, it is
|
||||
// probably not a good idea to fall-back to the O_PATH resolver. An
|
||||
// attacker could find a bug in the O_PATH resolver and uncontionally
|
||||
// falling back to the O_PATH resolver would form a downgrade attack.
|
||||
if handle, remainingPath, err := lookupOpenat2(root, unsafePath, partial); err == nil || linux.HasOpenat2() {
|
||||
return handle, remainingPath, err
|
||||
if hasOpenat2() {
|
||||
return lookupOpenat2(root, unsafePath, partial)
|
||||
}
|
||||
|
||||
// Get the "actual" root path from /proc/self/fd. This is necessary if the
|
||||
// root is some magic-link like /proc/$pid/root, in which case we want to
|
||||
// make sure when we do procfs.CheckProcSelfFdPath that we are using the
|
||||
// correct root path.
|
||||
logicalRootPath, err := procfs.ProcSelfFdReadlink(root)
|
||||
// make sure when we do checkProcSelfFdPath that we are using the correct
|
||||
// root path.
|
||||
logicalRootPath, err := procSelfFdReadlink(root)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("get real root path: %w", err)
|
||||
}
|
||||
|
||||
currentDir, err := fd.Dup(root)
|
||||
currentDir, err := dupFile(root)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
||||
}
|
||||
@@ -276,7 +260,7 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File,
|
||||
return nil, "", fmt.Errorf("walking into root with part %q failed: %w", part, err)
|
||||
}
|
||||
// Jump to root.
|
||||
rootClone, err := fd.Dup(root)
|
||||
rootClone, err := dupFile(root)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
||||
}
|
||||
@@ -287,21 +271,21 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File,
|
||||
}
|
||||
|
||||
// Try to open the next component.
|
||||
nextDir, err := fd.Openat(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||
switch err {
|
||||
case nil:
|
||||
nextDir, err := openatFile(currentDir, part, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||
switch {
|
||||
case err == nil:
|
||||
st, err := nextDir.Stat()
|
||||
if err != nil {
|
||||
_ = nextDir.Close()
|
||||
return nil, "", fmt.Errorf("stat component %q: %w", part, err)
|
||||
}
|
||||
|
||||
switch st.Mode() & os.ModeType { //nolint:exhaustive // just a glorified if statement
|
||||
switch st.Mode() & os.ModeType {
|
||||
case os.ModeSymlink:
|
||||
// readlinkat implies AT_EMPTY_PATH since Linux 2.6.39. See
|
||||
// Linux commit 65cfc6722361 ("readlinkat(), fchownat() and
|
||||
// fstatat() with empty relative pathnames").
|
||||
linkDest, err := fd.Readlinkat(nextDir, "")
|
||||
linkDest, err := readlinkatFile(nextDir, "")
|
||||
// We don't need the handle anymore.
|
||||
_ = nextDir.Close()
|
||||
if err != nil {
|
||||
@@ -309,7 +293,7 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File,
|
||||
}
|
||||
|
||||
linksWalked++
|
||||
if linksWalked > consts.MaxSymlinkLimit {
|
||||
if linksWalked > maxSymlinkLimit {
|
||||
return nil, "", &os.PathError{Op: "securejoin.lookupInRoot", Path: logicalRootPath + "/" + unsafePath, Err: unix.ELOOP}
|
||||
}
|
||||
|
||||
@@ -323,7 +307,7 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File,
|
||||
// Absolute symlinks reset any work we've already done.
|
||||
if path.IsAbs(linkDest) {
|
||||
// Jump to root.
|
||||
rootClone, err := fd.Dup(root)
|
||||
rootClone, err := dupFile(root)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("clone root fd: %w", err)
|
||||
}
|
||||
@@ -351,12 +335,12 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File,
|
||||
// rename or mount on the system.
|
||||
if part == ".." {
|
||||
// Make sure the root hasn't moved.
|
||||
if err := procfs.CheckProcSelfFdPath(logicalRootPath, root); err != nil {
|
||||
if err := checkProcSelfFdPath(logicalRootPath, root); err != nil {
|
||||
return nil, "", fmt.Errorf("root path moved during lookup: %w", err)
|
||||
}
|
||||
// Make sure the path is what we expect.
|
||||
fullPath := logicalRootPath + nextPath
|
||||
if err := procfs.CheckProcSelfFdPath(fullPath, currentDir); err != nil {
|
||||
if err := checkProcSelfFdPath(fullPath, currentDir); err != nil {
|
||||
return nil, "", fmt.Errorf("walking into %q had unexpected result: %w", part, err)
|
||||
}
|
||||
}
|
||||
@@ -387,7 +371,7 @@ func lookupInRoot(root fd.Fd, unsafePath string, partial bool) (Handle *os.File,
|
||||
// context of openat2, a trailing slash and a trailing "/." are completely
|
||||
// equivalent.
|
||||
if strings.HasSuffix(unsafePath, "/") {
|
||||
nextDir, err := fd.Openat(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||
nextDir, err := openatFile(currentDir, ".", unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
if !partial {
|
||||
_ = currentDir.Close()
|
||||
@@ -1,15 +1,10 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gopathrs
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -19,16 +14,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
|
||||
)
|
||||
|
||||
// ErrInvalidMode is returned from [MkdirAll] when the requested mode is
|
||||
// invalid.
|
||||
var ErrInvalidMode = errors.New("invalid permission mode")
|
||||
var (
|
||||
errInvalidMode = errors.New("invalid permission mode")
|
||||
errPossibleAttack = errors.New("possible attack detected")
|
||||
)
|
||||
|
||||
// modePermExt is like os.ModePerm except that it also includes the set[ug]id
|
||||
// and sticky bits.
|
||||
@@ -48,11 +39,11 @@ func toUnixMode(mode os.FileMode) (uint32, error) {
|
||||
}
|
||||
// We don't allow file type bits.
|
||||
if mode&os.ModeType != 0 {
|
||||
return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", ErrInvalidMode, mode, mode)
|
||||
return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", errInvalidMode, mode, mode)
|
||||
}
|
||||
// We don't allow other unknown modes.
|
||||
if mode&^modePermExt != 0 || sysMode&unix.S_IFMT != 0 {
|
||||
return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", ErrInvalidMode, mode, mode)
|
||||
return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", errInvalidMode, mode, mode)
|
||||
}
|
||||
return sysMode, nil
|
||||
}
|
||||
@@ -75,8 +66,6 @@ func toUnixMode(mode os.FileMode) (uint32, error) {
|
||||
// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after
|
||||
// doing [MkdirAll]. If you intend to open the directory after creating it, you
|
||||
// should use MkdirAllHandle.
|
||||
//
|
||||
// [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
|
||||
func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.File, Err error) {
|
||||
unixMode, err := toUnixMode(mode)
|
||||
if err != nil {
|
||||
@@ -87,11 +76,11 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
||||
// users it seems more prudent to return an error so users notice that
|
||||
// these bits will not be set.
|
||||
if unixMode&^0o1777 != 0 {
|
||||
return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", ErrInvalidMode, mode)
|
||||
return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", errInvalidMode, mode)
|
||||
}
|
||||
|
||||
// Try to open as much of the path as possible.
|
||||
currentDir, remainingPath, err := PartialLookupInRoot(root, unsafePath)
|
||||
currentDir, remainingPath, err := partialLookupInRoot(root, unsafePath)
|
||||
defer func() {
|
||||
if Err != nil {
|
||||
_ = currentDir.Close()
|
||||
@@ -113,24 +102,24 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
||||
//
|
||||
// This is mostly a quality-of-life check, because mkdir will simply fail
|
||||
// later if the attacker deletes the tree after this check.
|
||||
if err := fd.IsDeadInode(currentDir); err != nil {
|
||||
if err := isDeadInode(currentDir); err != nil {
|
||||
return nil, fmt.Errorf("finding existing subpath of %q: %w", unsafePath, err)
|
||||
}
|
||||
|
||||
// Re-open the path to match the O_DIRECTORY reopen loop later (so that we
|
||||
// always return a non-O_PATH handle). We also check that we actually got a
|
||||
// directory.
|
||||
if reopenDir, err := procfs.ReopenFd(currentDir, unix.O_DIRECTORY|unix.O_CLOEXEC); errors.Is(err, unix.ENOTDIR) {
|
||||
if reopenDir, err := Reopen(currentDir, unix.O_DIRECTORY|unix.O_CLOEXEC); errors.Is(err, unix.ENOTDIR) {
|
||||
return nil, fmt.Errorf("cannot create subdirectories in %q: %w", currentDir.Name(), unix.ENOTDIR)
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("re-opening handle to %q: %w", currentDir.Name(), err)
|
||||
} else { //nolint:revive // indent-error-flow lint doesn't make sense here
|
||||
} else {
|
||||
_ = currentDir.Close()
|
||||
currentDir = reopenDir
|
||||
}
|
||||
|
||||
remainingParts := strings.Split(remainingPath, string(filepath.Separator))
|
||||
if gocompat.SlicesContains(remainingParts, "..") {
|
||||
if slices_Contains(remainingParts, "..") {
|
||||
// The path contained ".." components after the end of the "real"
|
||||
// components. We could try to safely resolve ".." here but that would
|
||||
// add a bunch of extra logic for something that it's not clear even
|
||||
@@ -161,12 +150,12 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
||||
if err := unix.Mkdirat(int(currentDir.Fd()), part, unixMode); err != nil && !errors.Is(err, unix.EEXIST) {
|
||||
err = &os.PathError{Op: "mkdirat", Path: currentDir.Name() + "/" + part, Err: err}
|
||||
// Make the error a bit nicer if the directory is dead.
|
||||
if deadErr := fd.IsDeadInode(currentDir); deadErr != nil {
|
||||
if deadErr := isDeadInode(currentDir); deadErr != nil {
|
||||
// TODO: Once we bump the minimum Go version to 1.20, we can use
|
||||
// multiple %w verbs for this wrapping. For now we need to use a
|
||||
// compatibility shim for older Go versions.
|
||||
// err = fmt.Errorf("%w (%w)", err, deadErr)
|
||||
err = gocompat.WrapBaseError(err, deadErr)
|
||||
//err = fmt.Errorf("%w (%w)", err, deadErr)
|
||||
err = wrapBaseError(err, deadErr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -174,13 +163,13 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
||||
// Get a handle to the next component. O_DIRECTORY means we don't need
|
||||
// to use O_PATH.
|
||||
var nextDir *os.File
|
||||
if linux.HasOpenat2() {
|
||||
nextDir, err = openat2(currentDir, part, &unix.OpenHow{
|
||||
if hasOpenat2() {
|
||||
nextDir, err = openat2File(currentDir, part, &unix.OpenHow{
|
||||
Flags: unix.O_NOFOLLOW | unix.O_DIRECTORY | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_XDEV,
|
||||
})
|
||||
} else {
|
||||
nextDir, err = fd.Openat(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
nextDir, err = openatFile(currentDir, part, unix.O_NOFOLLOW|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -210,3 +199,38 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
|
||||
}
|
||||
return currentDir, nil
|
||||
}
|
||||
|
||||
// MkdirAll is a race-safe alternative to the [os.MkdirAll] function,
|
||||
// where the new directory is guaranteed to be within the root directory (if an
|
||||
// attacker can move directories from inside the root to outside the root, the
|
||||
// created directory tree might be outside of the root but the key constraint
|
||||
// is that at no point will we walk outside of the directory tree we are
|
||||
// creating).
|
||||
//
|
||||
// Effectively, MkdirAll(root, unsafePath, mode) is equivalent to
|
||||
//
|
||||
// path, _ := securejoin.SecureJoin(root, unsafePath)
|
||||
// err := os.MkdirAll(path, mode)
|
||||
//
|
||||
// But is much safer. The above implementation is unsafe because if an attacker
|
||||
// can modify the filesystem tree between [SecureJoin] and [os.MkdirAll], it is
|
||||
// possible for MkdirAll to resolve unsafe symlink components and create
|
||||
// directories outside of the root.
|
||||
//
|
||||
// If you plan to open the directory after you have created it or want to use
|
||||
// an open directory handle as the root, you should use [MkdirAllHandle] instead.
|
||||
// This function is a wrapper around [MkdirAllHandle].
|
||||
func MkdirAll(root, unsafePath string, mode os.FileMode) error {
|
||||
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rootDir.Close()
|
||||
|
||||
f, err := MkdirAllHandle(rootDir, unsafePath, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = f.Close()
|
||||
return nil
|
||||
}
|
||||
103
vendor/github.com/cyphar/filepath-securejoin/open_linux.go
generated
vendored
Normal file
103
vendor/github.com/cyphar/filepath-securejoin/open_linux.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided
|
||||
// using an *[os.File] handle, to ensure that the correct root directory is used.
|
||||
func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
||||
handle, err := completeLookupInRoot(root, unsafePath)
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: err}
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// OpenInRoot safely opens the provided unsafePath within the root.
|
||||
// Effectively, OpenInRoot(root, unsafePath) is equivalent to
|
||||
//
|
||||
// path, _ := securejoin.SecureJoin(root, unsafePath)
|
||||
// handle, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC)
|
||||
//
|
||||
// But is much safer. The above implementation is unsafe because if an attacker
|
||||
// can modify the filesystem tree between [SecureJoin] and [os.OpenFile], it is
|
||||
// possible for the returned file to be outside of the root.
|
||||
//
|
||||
// Note that the returned handle is an O_PATH handle, meaning that only a very
|
||||
// limited set of operations will work on the handle. This is done to avoid
|
||||
// accidentally opening an untrusted file that could cause issues (such as a
|
||||
// disconnected TTY that could cause a DoS, or some other issue). In order to
|
||||
// use the returned handle, you can "upgrade" it to a proper handle using
|
||||
// [Reopen].
|
||||
func OpenInRoot(root, unsafePath string) (*os.File, error) {
|
||||
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rootDir.Close()
|
||||
return OpenatInRoot(rootDir, unsafePath)
|
||||
}
|
||||
|
||||
// Reopen takes an *[os.File] handle and re-opens it through /proc/self/fd.
|
||||
// Reopen(file, flags) is effectively equivalent to
|
||||
//
|
||||
// fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd())
|
||||
// os.OpenFile(fdPath, flags|unix.O_CLOEXEC)
|
||||
//
|
||||
// But with some extra hardenings to ensure that we are not tricked by a
|
||||
// maliciously-configured /proc mount. While this attack scenario is not
|
||||
// common, in container runtimes it is possible for higher-level runtimes to be
|
||||
// tricked into configuring an unsafe /proc that can be used to attack file
|
||||
// operations. See [CVE-2019-19921] for more details.
|
||||
//
|
||||
// [CVE-2019-19921]: https://github.com/advisories/GHSA-fh74-hm69-rqjw
|
||||
func Reopen(handle *os.File, flags int) (*os.File, error) {
|
||||
procRoot, err := getProcRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We can't operate on /proc/thread-self/fd/$n directly when doing a
|
||||
// re-open, so we need to open /proc/thread-self/fd and then open a single
|
||||
// final component.
|
||||
procFdDir, closer, err := procThreadSelf(procRoot, "fd/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get safe /proc/thread-self/fd handle: %w", err)
|
||||
}
|
||||
defer procFdDir.Close()
|
||||
defer closer()
|
||||
|
||||
// Try to detect if there is a mount on top of the magic-link we are about
|
||||
// to open. If we are using unsafeHostProcRoot(), this could change after
|
||||
// we check it (and there's nothing we can do about that) but for
|
||||
// privateProcRoot() this should be guaranteed to be safe (at least since
|
||||
// Linux 5.12[1], when anonymous mount namespaces were completely isolated
|
||||
// from external mounts including mount propagation events).
|
||||
//
|
||||
// [1]: Linux commit ee2e3f50629f ("mount: fix mounting of detached mounts
|
||||
// onto targets that reside on shared mounts").
|
||||
fdStr := strconv.Itoa(int(handle.Fd()))
|
||||
if err := checkSymlinkOvermount(procRoot, procFdDir, fdStr); err != nil {
|
||||
return nil, fmt.Errorf("check safety of /proc/thread-self/fd/%s magiclink: %w", fdStr, err)
|
||||
}
|
||||
|
||||
flags |= unix.O_CLOEXEC
|
||||
// Rather than just wrapping openatFile, open-code it so we can copy
|
||||
// handle.Name().
|
||||
reopenFd, err := unix.Openat(int(procFdDir.Fd()), fdStr, flags, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reopen fd %d: %w", handle.Fd(), err)
|
||||
}
|
||||
return os.NewFile(uintptr(reopenFd), handle.Name()), nil
|
||||
}
|
||||
127
vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go
generated
vendored
Normal file
127
vendor/github.com/cyphar/filepath-securejoin/openat2_linux.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var hasOpenat2 = sync_OnceValue(func() bool {
|
||||
fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT,
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_ = unix.Close(fd)
|
||||
return true
|
||||
})
|
||||
|
||||
func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool {
|
||||
// RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve
|
||||
// ".." while a mount or rename occurs anywhere on the system. This could
|
||||
// happen spuriously, or as the result of an attacker trying to mess with
|
||||
// us during lookup.
|
||||
//
|
||||
// In addition, scoped lookups have a "safety check" at the end of
|
||||
// complete_walk which will return -EXDEV if the final path is not in the
|
||||
// root.
|
||||
return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 &&
|
||||
(errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV))
|
||||
}
|
||||
|
||||
const scopedLookupMaxRetries = 10
|
||||
|
||||
func openat2File(dir *os.File, path string, how *unix.OpenHow) (*os.File, error) {
|
||||
fullPath := dir.Name() + "/" + path
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
how.Flags |= unix.O_CLOEXEC
|
||||
var tries int
|
||||
for tries < scopedLookupMaxRetries {
|
||||
fd, err := unix.Openat2(int(dir.Fd()), path, how)
|
||||
if err != nil {
|
||||
if scopedLookupShouldRetry(how, err) {
|
||||
// We retry a couple of times to avoid the spurious errors, and
|
||||
// if we are being attacked then returning -EAGAIN is the best
|
||||
// we can do.
|
||||
tries++
|
||||
continue
|
||||
}
|
||||
return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err}
|
||||
}
|
||||
// If we are using RESOLVE_IN_ROOT, the name we generated may be wrong.
|
||||
// NOTE: The procRoot code MUST NOT use RESOLVE_IN_ROOT, otherwise
|
||||
// you'll get infinite recursion here.
|
||||
if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT {
|
||||
if actualPath, err := rawProcSelfFdReadlink(fd); err == nil {
|
||||
fullPath = actualPath
|
||||
}
|
||||
}
|
||||
return os.NewFile(uintptr(fd), fullPath), nil
|
||||
}
|
||||
return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: errPossibleAttack}
|
||||
}
|
||||
|
||||
func lookupOpenat2(root *os.File, unsafePath string, partial bool) (*os.File, string, error) {
|
||||
if !partial {
|
||||
file, err := openat2File(root, unsafePath, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
|
||||
})
|
||||
return file, "", err
|
||||
}
|
||||
return partialLookupOpenat2(root, unsafePath)
|
||||
}
|
||||
|
||||
// partialLookupOpenat2 is an alternative implementation of
|
||||
// partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a
|
||||
// handle to the deepest existing child of the requested path within the root.
|
||||
func partialLookupOpenat2(root *os.File, unsafePath string) (*os.File, string, error) {
|
||||
// TODO: Implement this as a git-bisect-like binary search.
|
||||
|
||||
unsafePath = filepath.ToSlash(unsafePath) // noop
|
||||
endIdx := len(unsafePath)
|
||||
var lastError error
|
||||
for endIdx > 0 {
|
||||
subpath := unsafePath[:endIdx]
|
||||
|
||||
handle, err := openat2File(root, subpath, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
|
||||
})
|
||||
if err == nil {
|
||||
// Jump over the slash if we have a non-"" remainingPath.
|
||||
if endIdx < len(unsafePath) {
|
||||
endIdx += 1
|
||||
}
|
||||
// We found a subpath!
|
||||
return handle, unsafePath[endIdx:], lastError
|
||||
}
|
||||
if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) {
|
||||
// That path doesn't exist, let's try the next directory up.
|
||||
endIdx = strings.LastIndexByte(subpath, '/')
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
return nil, "", fmt.Errorf("open subpath: %w", err)
|
||||
}
|
||||
// If we couldn't open anything, the whole subpath is missing. Return a
|
||||
// copy of the root fd so that the caller doesn't close this one by
|
||||
// accident.
|
||||
rootClone, err := dupFile(root)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return rootClone, unsafePath, lastError
|
||||
}
|
||||
59
vendor/github.com/cyphar/filepath-securejoin/openat_linux.go
generated
vendored
Normal file
59
vendor/github.com/cyphar/filepath-securejoin/openat_linux.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securejoin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func dupFile(f *os.File) (*os.File, error) {
|
||||
fd, err := unix.FcntlInt(f.Fd(), unix.F_DUPFD_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err)
|
||||
}
|
||||
return os.NewFile(uintptr(fd), f.Name()), nil
|
||||
}
|
||||
|
||||
func openatFile(dir *os.File, path string, flags int, mode int) (*os.File, error) {
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.O_CLOEXEC
|
||||
fd, err := unix.Openat(int(dir.Fd()), path, flags, uint32(mode))
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "openat", Path: dir.Name() + "/" + path, Err: err}
|
||||
}
|
||||
// All of the paths we use with openatFile(2) are guaranteed to be
|
||||
// lexically safe, so we can use path.Join here.
|
||||
fullPath := filepath.Join(dir.Name(), path)
|
||||
return os.NewFile(uintptr(fd), fullPath), nil
|
||||
}
|
||||
|
||||
func fstatatFile(dir *os.File, path string, flags int) (unix.Stat_t, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Fstatat(int(dir.Fd()), path, &stat, flags); err != nil {
|
||||
return stat, &os.PathError{Op: "fstatat", Path: dir.Name() + "/" + path, Err: err}
|
||||
}
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
func readlinkatFile(dir *os.File, path string) (string, error) {
|
||||
size := 4096
|
||||
for {
|
||||
linkBuf := make([]byte, size)
|
||||
n, err := unix.Readlinkat(int(dir.Fd()), path, linkBuf)
|
||||
if err != nil {
|
||||
return "", &os.PathError{Op: "readlinkat", Path: dir.Name() + "/" + path, Err: err}
|
||||
}
|
||||
if n != size {
|
||||
return string(linkBuf[:n]), nil
|
||||
}
|
||||
// Possible truncation, resize the buffer.
|
||||
size *= 2
|
||||
}
|
||||
}
|
||||
35
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
generated
vendored
35
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/README.md
generated
vendored
@@ -1,35 +0,0 @@
|
||||
## `pathrs-lite` ##
|
||||
|
||||
`github.com/cyphar/filepath-securejoin/pathrs-lite` provides a minimal **pure
|
||||
Go** implementation of the core bits of [libpathrs][]. This is not intended to
|
||||
be a complete replacement for libpathrs, instead it is mainly intended to be
|
||||
useful as a transition tool for existing Go projects.
|
||||
|
||||
`pathrs-lite` also provides a very easy way to switch to `libpathrs` (even for
|
||||
downstreams where `pathrs-lite` is being used in a third-party package and is
|
||||
not interested in using CGo). At build time, if you use the `libpathrs` build
|
||||
tag then `pathrs-lite` will use `libpathrs` directly instead of the pure Go
|
||||
implementation. The two backends are functionally equivalent (and we have
|
||||
integration tests to verify this), so this migration should be very easy with
|
||||
no user-visible impact.
|
||||
|
||||
[libpathrs]: https://github.com/cyphar/libpathrs
|
||||
|
||||
### License ###
|
||||
|
||||
Most of this subpackage is licensed under the Mozilla Public License (version
|
||||
2.0). For more information, see the top-level [COPYING.md][] and
|
||||
[LICENSE.MPL-2.0][] files, as well as the individual license headers for each
|
||||
file.
|
||||
|
||||
```
|
||||
Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
Copyright (C) 2024-2025 SUSE LLC
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
```
|
||||
|
||||
[COPYING.md]: ../COPYING.md
|
||||
[LICENSE.MPL-2.0]: ../LICENSE.MPL-2.0
|
||||
16
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
generated
vendored
16
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/doc.go
generated
vendored
@@ -1,16 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package pathrs (pathrs-lite) is a less complete pure Go implementation of
|
||||
// some of the APIs provided by [libpathrs].
|
||||
//
|
||||
// [libpathrs]: https://github.com/cyphar/libpathrs
|
||||
package pathrs
|
||||
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
generated
vendored
30
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/assert/assert.go
generated
vendored
@@ -1,30 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package assert provides some basic assertion helpers for Go.
|
||||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Assert panics if the predicate is false with the provided argument.
|
||||
func Assert(predicate bool, msg any) {
|
||||
if !predicate {
|
||||
panic(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Assertf panics if the predicate is false and formats the message using the
|
||||
// same formatting as [fmt.Printf].
|
||||
//
|
||||
// [fmt.Printf]: https://pkg.go.dev/fmt#Printf
|
||||
func Assertf(predicate bool, fmtMsg string, args ...any) {
|
||||
Assert(predicate, fmt.Sprintf(fmtMsg, args...))
|
||||
}
|
||||
41
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go
generated
vendored
41
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/errors_linux.go
generated
vendored
@@ -1,41 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package internal contains unexported common code for filepath-securejoin.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type xdevErrorish struct {
|
||||
description string
|
||||
}
|
||||
|
||||
func (err xdevErrorish) Error() string { return err.description }
|
||||
func (err xdevErrorish) Is(target error) bool { return target == unix.EXDEV }
|
||||
|
||||
var (
|
||||
// ErrPossibleAttack indicates that some attack was detected.
|
||||
ErrPossibleAttack error = xdevErrorish{"possible attack detected"}
|
||||
|
||||
// ErrPossibleBreakout indicates that during an operation we ended up in a
|
||||
// state that could be a breakout but we detected it.
|
||||
ErrPossibleBreakout error = xdevErrorish{"possible breakout detected"}
|
||||
|
||||
// ErrInvalidDirectory indicates an unlinked directory.
|
||||
ErrInvalidDirectory = errors.New("wandered into deleted directory")
|
||||
|
||||
// ErrDeletedInode indicates an unlinked file (non-directory).
|
||||
ErrDeletedInode = errors.New("cannot verify path of deleted inode")
|
||||
)
|
||||
148
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
generated
vendored
148
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/at_linux.go
generated
vendored
@@ -1,148 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package fd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
)
|
||||
|
||||
// prepareAtWith returns -EBADF (an invalid fd) if dir is nil, otherwise using
|
||||
// the dir.Fd(). We use -EBADF because in filepath-securejoin we generally
|
||||
// don't want to allow relative-to-cwd paths. The returned path is an
|
||||
// *informational* string that describes a reasonable pathname for the given
|
||||
// *at(2) arguments. You must not use the full path for any actual filesystem
|
||||
// operations.
|
||||
func prepareAt(dir Fd, path string) (dirFd int, unsafeUnmaskedPath string) {
|
||||
dirFd, dirPath := -int(unix.EBADF), "."
|
||||
if dir != nil {
|
||||
dirFd, dirPath = int(dir.Fd()), dir.Name()
|
||||
}
|
||||
if !filepath.IsAbs(path) {
|
||||
// only prepend the dirfd path for relative paths
|
||||
path = dirPath + "/" + path
|
||||
}
|
||||
// NOTE: If path is "." or "", the returned path won't be filepath.Clean,
|
||||
// but that's okay since this path is either used for errors (in which case
|
||||
// a trailing "/" or "/." is important information) or will be
|
||||
// filepath.Clean'd later (in the case of fd.Openat).
|
||||
return dirFd, path
|
||||
}
|
||||
|
||||
// Openat is an [Fd]-based wrapper around unix.Openat.
|
||||
func Openat(dir Fd, path string, flags int, mode int) (*os.File, error) { //nolint:unparam // wrapper func
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.O_CLOEXEC
|
||||
fd, err := unix.Openat(dirFd, path, flags, uint32(mode))
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
// openat is only used with lexically-safe paths so we can use
|
||||
// filepath.Clean here, and also the path itself is not going to be used
|
||||
// for actual path operations.
|
||||
fullPath = filepath.Clean(fullPath)
|
||||
return os.NewFile(uintptr(fd), fullPath), nil
|
||||
}
|
||||
|
||||
// Fstatat is an [Fd]-based wrapper around unix.Fstatat.
|
||||
func Fstatat(dir Fd, path string, flags int) (unix.Stat_t, error) {
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Fstatat(dirFd, path, &stat, flags); err != nil {
|
||||
return stat, &os.PathError{Op: "fstatat", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
// Faccessat is an [Fd]-based wrapper around unix.Faccessat.
|
||||
func Faccessat(dir Fd, path string, mode uint32, flags int) error {
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
err := unix.Faccessat(dirFd, path, mode, flags)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "faccessat", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
return err
|
||||
}
|
||||
|
||||
// Readlinkat is an [Fd]-based wrapper around unix.Readlinkat.
|
||||
func Readlinkat(dir Fd, path string) (string, error) {
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
size := 4096
|
||||
for {
|
||||
linkBuf := make([]byte, size)
|
||||
n, err := unix.Readlinkat(dirFd, path, linkBuf)
|
||||
if err != nil {
|
||||
return "", &os.PathError{Op: "readlinkat", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
if n != size {
|
||||
return string(linkBuf[:n]), nil
|
||||
}
|
||||
// Possible truncation, resize the buffer.
|
||||
size *= 2
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to
|
||||
// avoid bumping the requirement for a single constant we can just define it
|
||||
// ourselves.
|
||||
_STATX_MNT_ID_UNIQUE = 0x4000 //nolint:revive // unix.* name
|
||||
|
||||
// We don't care which mount ID we get. The kernel will give us the unique
|
||||
// one if it is supported. If the kernel doesn't support
|
||||
// STATX_MNT_ID_UNIQUE, the bit is ignored and the returned request mask
|
||||
// will only contain STATX_MNT_ID (if supported).
|
||||
wantStatxMntMask = _STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID
|
||||
)
|
||||
|
||||
var hasStatxMountID = gocompat.SyncOnceValue(func() bool {
|
||||
var stx unix.Statx_t
|
||||
err := unix.Statx(-int(unix.EBADF), "/", 0, wantStatxMntMask, &stx)
|
||||
return err == nil && stx.Mask&wantStatxMntMask != 0
|
||||
})
|
||||
|
||||
// GetMountID gets the mount identifier associated with the fd and path
|
||||
// combination. It is effectively a wrapper around fetching
|
||||
// STATX_MNT_ID{,_UNIQUE} with unix.Statx, but with a fallback to 0 if the
|
||||
// kernel doesn't support the feature.
|
||||
func GetMountID(dir Fd, path string) (uint64, error) {
|
||||
// If we don't have statx(STATX_MNT_ID*) support, we can't do anything.
|
||||
if !hasStatxMountID() {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
|
||||
var stx unix.Statx_t
|
||||
err := unix.Statx(dirFd, path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, wantStatxMntMask, &stx)
|
||||
if stx.Mask&wantStatxMntMask == 0 {
|
||||
// It's not a kernel limitation, for some reason we couldn't get a
|
||||
// mount ID. Assume it's some kind of attack.
|
||||
err = fmt.Errorf("could not get mount id: %w", err)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
return stx.Mnt_id, nil
|
||||
}
|
||||
55
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
generated
vendored
55
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd.go
generated
vendored
@@ -1,55 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package fd provides a drop-in interface-based replacement of [*os.File] that
|
||||
// allows for things like noop-Close wrappers to be used.
|
||||
//
|
||||
// [*os.File]: https://pkg.go.dev/os#File
|
||||
package fd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Fd is an interface that mirrors most of the API of [*os.File], allowing you
|
||||
// to create wrappers that can be used in place of [*os.File].
|
||||
//
|
||||
// [*os.File]: https://pkg.go.dev/os#File
|
||||
type Fd interface {
|
||||
io.Closer
|
||||
Name() string
|
||||
Fd() uintptr
|
||||
}
|
||||
|
||||
// Compile-time interface checks.
|
||||
var (
|
||||
_ Fd = (*os.File)(nil)
|
||||
_ Fd = noClose{}
|
||||
)
|
||||
|
||||
type noClose struct{ inner Fd }
|
||||
|
||||
func (f noClose) Name() string { return f.inner.Name() }
|
||||
func (f noClose) Fd() uintptr { return f.inner.Fd() }
|
||||
|
||||
func (f noClose) Close() error { return nil }
|
||||
|
||||
// NopCloser returns an [*os.File]-like object where the [Close] method is now
|
||||
// a no-op.
|
||||
//
|
||||
// Note that for [*os.File] and similar objects, the Go garbage collector will
|
||||
// still call [Close] on the underlying file unless you use
|
||||
// [runtime.SetFinalizer] to disable this behaviour. This is up to the caller
|
||||
// to do (if necessary).
|
||||
//
|
||||
// [*os.File]: https://pkg.go.dev/os#File
|
||||
// [Close]: https://pkg.go.dev/io#Closer
|
||||
// [runtime.SetFinalizer]: https://pkg.go.dev/runtime#SetFinalizer
|
||||
func NopCloser(f Fd) Fd { return noClose{inner: f} }
|
||||
78
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
generated
vendored
78
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/fd_linux.go
generated
vendored
@@ -1,78 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package fd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal"
|
||||
)
|
||||
|
||||
// DupWithName creates a new file descriptor referencing the same underlying
|
||||
// file, but with the provided name instead of fd.Name().
|
||||
func DupWithName(fd Fd, name string) (*os.File, error) {
|
||||
fd2, err := unix.FcntlInt(fd.Fd(), unix.F_DUPFD_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err)
|
||||
}
|
||||
runtime.KeepAlive(fd)
|
||||
return os.NewFile(uintptr(fd2), name), nil
|
||||
}
|
||||
|
||||
// Dup creates a new file description referencing the same underlying file.
|
||||
func Dup(fd Fd) (*os.File, error) {
|
||||
return DupWithName(fd, fd.Name())
|
||||
}
|
||||
|
||||
// Fstat is an [Fd]-based wrapper around unix.Fstat.
|
||||
func Fstat(fd Fd) (unix.Stat_t, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Fstat(int(fd.Fd()), &stat); err != nil {
|
||||
return stat, &os.PathError{Op: "fstat", Path: fd.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(fd)
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
// Fstatfs is an [Fd]-based wrapper around unix.Fstatfs.
|
||||
func Fstatfs(fd Fd) (unix.Statfs_t, error) {
|
||||
var statfs unix.Statfs_t
|
||||
if err := unix.Fstatfs(int(fd.Fd()), &statfs); err != nil {
|
||||
return statfs, &os.PathError{Op: "fstatfs", Path: fd.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(fd)
|
||||
return statfs, nil
|
||||
}
|
||||
|
||||
// IsDeadInode detects whether the file has been unlinked from a filesystem and
|
||||
// is thus a "dead inode" from the kernel's perspective.
|
||||
func IsDeadInode(file Fd) error {
|
||||
// If the nlink of a file drops to 0, there is an attacker deleting
|
||||
// directories during our walk, which could result in weird /proc values.
|
||||
// It's better to error out in this case.
|
||||
stat, err := Fstat(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check for dead inode: %w", err)
|
||||
}
|
||||
if stat.Nlink == 0 {
|
||||
err := internal.ErrDeletedInode
|
||||
if stat.Mode&unix.S_IFMT == unix.S_IFDIR {
|
||||
err = internal.ErrInvalidDirectory
|
||||
}
|
||||
return fmt.Errorf("%w %q", err, file.Name())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
54
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
generated
vendored
54
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/mount_linux.go
generated
vendored
@@ -1,54 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package fd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Fsopen is an [Fd]-based wrapper around unix.Fsopen.
|
||||
func Fsopen(fsName string, flags int) (*os.File, error) {
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.FSOPEN_CLOEXEC
|
||||
fd, err := unix.Fsopen(fsName, flags)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("fsopen "+fsName, err)
|
||||
}
|
||||
return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil
|
||||
}
|
||||
|
||||
// Fsmount is an [Fd]-based wrapper around unix.Fsmount.
|
||||
func Fsmount(ctx Fd, flags, mountAttrs int) (*os.File, error) {
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.FSMOUNT_CLOEXEC
|
||||
fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs)
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("fsmount "+ctx.Name(), err)
|
||||
}
|
||||
return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil
|
||||
}
|
||||
|
||||
// OpenTree is an [Fd]-based wrapper around unix.OpenTree.
|
||||
func OpenTree(dir Fd, path string, flags uint) (*os.File, error) {
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
flags |= unix.OPEN_TREE_CLOEXEC
|
||||
fd, err := unix.OpenTree(dirFd, path, flags)
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "open_tree", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
return os.NewFile(uintptr(fd), fullPath), nil
|
||||
}
|
||||
64
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
generated
vendored
64
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd/openat2_linux.go
generated
vendored
@@ -1,64 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package fd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool {
|
||||
// RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve
|
||||
// ".." while a mount or rename occurs anywhere on the system. This could
|
||||
// happen spuriously, or as the result of an attacker trying to mess with
|
||||
// us during lookup.
|
||||
//
|
||||
// In addition, scoped lookups have a "safety check" at the end of
|
||||
// complete_walk which will return -EXDEV if the final path is not in the
|
||||
// root.
|
||||
return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 &&
|
||||
(errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV))
|
||||
}
|
||||
|
||||
// This is a fairly arbitrary limit we have just to avoid an attacker being
|
||||
// able to make us spin in an infinite retry loop -- callers can choose to
|
||||
// retry on EAGAIN if they prefer.
|
||||
const scopedLookupMaxRetries = 128
|
||||
|
||||
// Openat2 is an [Fd]-based wrapper around unix.Openat2, but with some retry
|
||||
// logic in case of EAGAIN errors.
|
||||
//
|
||||
// NOTE: This is a variable so that the lookup tests can force openat2 to fail.
|
||||
var Openat2 = func(dir Fd, path string, how *unix.OpenHow) (*os.File, error) {
|
||||
dirFd, fullPath := prepareAt(dir, path)
|
||||
// Make sure we always set O_CLOEXEC.
|
||||
how.Flags |= unix.O_CLOEXEC
|
||||
var tries int
|
||||
for {
|
||||
fd, err := unix.Openat2(dirFd, path, how)
|
||||
if err != nil {
|
||||
if scopedLookupShouldRetry(how, err) && tries < scopedLookupMaxRetries {
|
||||
// We retry a couple of times to avoid the spurious errors, and
|
||||
// if we are being attacked then returning -EAGAIN is the best
|
||||
// we can do.
|
||||
tries++
|
||||
continue
|
||||
}
|
||||
return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err}
|
||||
}
|
||||
runtime.KeepAlive(dir)
|
||||
return os.NewFile(uintptr(fd), fullPath), nil
|
||||
}
|
||||
}
|
||||
10
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
generated
vendored
10
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/README.md
generated
vendored
@@ -1,10 +0,0 @@
|
||||
## gocompat ##
|
||||
|
||||
This directory contains backports of stdlib functions from later Go versions so
|
||||
the filepath-securejoin can continue to be used by projects that are stuck with
|
||||
Go 1.18 support. Note that often filepath-securejoin is added in security
|
||||
patches for old releases, so avoiding the need to bump Go compiler requirements
|
||||
is a huge plus to downstreams.
|
||||
|
||||
The source code is licensed under the same license as the Go stdlib. See the
|
||||
source files for the precise license information.
|
||||
13
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
generated
vendored
13
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat/doc.go
generated
vendored
@@ -1,13 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//go:build linux && go1.20
|
||||
|
||||
// Copyright (C) 2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package gocompat includes compatibility shims (backported from future Go
|
||||
// stdlib versions) to permit filepath-securejoin to be used with older Go
|
||||
// versions (often filepath-securejoin is added in security patches for old
|
||||
// releases, so avoiding the need to bump Go compiler requirements is a huge
|
||||
// plus to downstreams).
|
||||
package gocompat
|
||||
@@ -1,19 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && go1.19
|
||||
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gocompat
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// A Bool is an atomic boolean value.
|
||||
// The zero value is false.
|
||||
//
|
||||
// Bool must not be copied after first use.
|
||||
type Bool = atomic.Bool
|
||||
@@ -1,48 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !go1.19
|
||||
|
||||
// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gocompat
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// noCopy may be added to structs which must not be copied
|
||||
// after the first use.
|
||||
//
|
||||
// See https://golang.org/issues/8005#issuecomment-190753527
|
||||
// for details.
|
||||
//
|
||||
// Note that it must not be embedded, due to the Lock and Unlock methods.
|
||||
type noCopy struct{}
|
||||
|
||||
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||
func (*noCopy) Lock() {}
|
||||
|
||||
// b32 returns a uint32 0 or 1 representing b.
|
||||
func b32(b bool) uint32 {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// A Bool is an atomic boolean value.
|
||||
// The zero value is false.
|
||||
//
|
||||
// Bool must not be copied after first use.
|
||||
type Bool struct {
|
||||
_ noCopy
|
||||
v uint32
|
||||
}
|
||||
|
||||
// Load atomically loads and returns the value stored in x.
|
||||
func (x *Bool) Load() bool { return atomic.LoadUint32(&x.v) != 0 }
|
||||
|
||||
// Store atomically stores val into x.
|
||||
func (x *Bool) Store(val bool) { atomic.StoreUint32(&x.v, b32(val)) }
|
||||
@@ -1,53 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && go1.21
|
||||
|
||||
// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gocompat
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc.
|
||||
func SlicesDeleteFunc[S ~[]E, E any](slice S, delFn func(E) bool) S {
|
||||
return slices.DeleteFunc(slice, delFn)
|
||||
}
|
||||
|
||||
// SlicesContains is equivalent to Go 1.21's slices.Contains.
|
||||
func SlicesContains[S ~[]E, E comparable](slice S, val E) bool {
|
||||
return slices.Contains(slice, val)
|
||||
}
|
||||
|
||||
// SlicesClone is equivalent to Go 1.21's slices.Clone.
|
||||
func SlicesClone[S ~[]E, E any](slice S) S {
|
||||
return slices.Clone(slice)
|
||||
}
|
||||
|
||||
// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue.
|
||||
func SyncOnceValue[T any](f func() T) func() T {
|
||||
return sync.OnceValue(f)
|
||||
}
|
||||
|
||||
// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
|
||||
func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
||||
return sync.OnceValues(f)
|
||||
}
|
||||
|
||||
// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
|
||||
type CmpOrdered = cmp.Ordered
|
||||
|
||||
// CmpCompare is equivalent to Go 1.21's cmp.Compare.
|
||||
func CmpCompare[T CmpOrdered](x, y T) int {
|
||||
return cmp.Compare(x, y)
|
||||
}
|
||||
|
||||
// Max2 is equivalent to Go 1.21's max builtin (but only for two parameters).
|
||||
func Max2[T CmpOrdered](x, y T) T {
|
||||
return max(x, y)
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !go1.21
|
||||
|
||||
// Copyright (C) 2021, 2022 The Go Authors. All rights reserved.
|
||||
// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.BSD file.
|
||||
|
||||
package gocompat
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// These are very minimal implementations of functions that appear in Go 1.21's
|
||||
// stdlib, included so that we can build on older Go versions. Most are
|
||||
// borrowed directly from the stdlib, and a few are modified to be "obviously
|
||||
// correct" without needing to copy too many other helpers.
|
||||
|
||||
// clearSlice is equivalent to Go 1.21's builtin clear.
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func clearSlice[S ~[]E, E any](slice S) {
|
||||
var zero E
|
||||
for i := range slice {
|
||||
slice[i] = zero
|
||||
}
|
||||
}
|
||||
|
||||
// slicesIndexFunc is equivalent to Go 1.21's slices.IndexFunc.
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func slicesIndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
|
||||
for i := range s {
|
||||
if f(s[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// SlicesDeleteFunc is equivalent to Go 1.21's slices.DeleteFunc.
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func SlicesDeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
|
||||
i := slicesIndexFunc(s, del)
|
||||
if i == -1 {
|
||||
return s
|
||||
}
|
||||
// Don't start copying elements until we find one to delete.
|
||||
for j := i + 1; j < len(s); j++ {
|
||||
if v := s[j]; !del(v) {
|
||||
s[i] = v
|
||||
i++
|
||||
}
|
||||
}
|
||||
clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
|
||||
return s[:i]
|
||||
}
|
||||
|
||||
// SlicesContains is equivalent to Go 1.21's slices.Contains.
|
||||
// Similar to the stdlib slices.Contains, except that we don't have
|
||||
// slices.Index so we need to use slices.IndexFunc for this non-Func helper.
|
||||
func SlicesContains[S ~[]E, E comparable](s S, v E) bool {
|
||||
return slicesIndexFunc(s, func(e E) bool { return e == v }) >= 0
|
||||
}
|
||||
|
||||
// SlicesClone is equivalent to Go 1.21's slices.Clone.
|
||||
// Copied from the Go 1.24 stdlib implementation.
|
||||
func SlicesClone[S ~[]E, E any](s S) S {
|
||||
// Preserve nil in case it matters.
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return append(S([]E{}), s...)
|
||||
}
|
||||
|
||||
// SyncOnceValue is equivalent to Go 1.21's sync.OnceValue.
|
||||
// Copied from the Go 1.25 stdlib implementation.
|
||||
func SyncOnceValue[T any](f func() T) func() T {
|
||||
// Use a struct so that there's a single heap allocation.
|
||||
d := struct {
|
||||
f func() T
|
||||
once sync.Once
|
||||
valid bool
|
||||
p any
|
||||
result T
|
||||
}{
|
||||
f: f,
|
||||
}
|
||||
return func() T {
|
||||
d.once.Do(func() {
|
||||
defer func() {
|
||||
d.f = nil
|
||||
d.p = recover()
|
||||
if !d.valid {
|
||||
panic(d.p)
|
||||
}
|
||||
}()
|
||||
d.result = d.f()
|
||||
d.valid = true
|
||||
})
|
||||
if !d.valid {
|
||||
panic(d.p)
|
||||
}
|
||||
return d.result
|
||||
}
|
||||
}
|
||||
|
||||
// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
|
||||
// Copied from the Go 1.25 stdlib implementation.
|
||||
func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
||||
// Use a struct so that there's a single heap allocation.
|
||||
d := struct {
|
||||
f func() (T1, T2)
|
||||
once sync.Once
|
||||
valid bool
|
||||
p any
|
||||
r1 T1
|
||||
r2 T2
|
||||
}{
|
||||
f: f,
|
||||
}
|
||||
return func() (T1, T2) {
|
||||
d.once.Do(func() {
|
||||
defer func() {
|
||||
d.f = nil
|
||||
d.p = recover()
|
||||
if !d.valid {
|
||||
panic(d.p)
|
||||
}
|
||||
}()
|
||||
d.r1, d.r2 = d.f()
|
||||
d.valid = true
|
||||
})
|
||||
if !d.valid {
|
||||
panic(d.p)
|
||||
}
|
||||
return d.r1, d.r2
|
||||
}
|
||||
}
|
||||
|
||||
// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
|
||||
// Copied from the Go 1.25 stdlib implementation.
|
||||
type CmpOrdered interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64 |
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
|
||||
~float32 | ~float64 |
|
||||
~string
|
||||
}
|
||||
|
||||
// isNaN reports whether x is a NaN without requiring the math package.
|
||||
// This will always return false if T is not floating-point.
|
||||
// Copied from the Go 1.25 stdlib implementation.
|
||||
func isNaN[T CmpOrdered](x T) bool {
|
||||
return x != x
|
||||
}
|
||||
|
||||
// CmpCompare is equivalent to Go 1.21's cmp.Compare.
|
||||
// Copied from the Go 1.25 stdlib implementation.
|
||||
func CmpCompare[T CmpOrdered](x, y T) int {
|
||||
xNaN := isNaN(x)
|
||||
yNaN := isNaN(y)
|
||||
if xNaN {
|
||||
if yNaN {
|
||||
return 0
|
||||
}
|
||||
return -1
|
||||
}
|
||||
if yNaN {
|
||||
return +1
|
||||
}
|
||||
if x < y {
|
||||
return -1
|
||||
}
|
||||
if x > y {
|
||||
return +1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Max2 is equivalent to Go 1.21's max builtin for two parameters.
|
||||
func Max2[T CmpOrdered](x, y T) T {
|
||||
m := x
|
||||
if y > m {
|
||||
m = y
|
||||
}
|
||||
return m
|
||||
}
|
||||
16
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go
generated
vendored
16
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/doc.go
generated
vendored
@@ -1,16 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package gopathrs is a less complete pure Go implementation of some of the
|
||||
// APIs provided by [libpathrs].
|
||||
//
|
||||
// [libpathrs]: https://github.com/cyphar/libpathrs
|
||||
package gopathrs
|
||||
26
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go
generated
vendored
26
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/open_linux.go
generated
vendored
@@ -1,26 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package gopathrs
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// OpenatInRoot is equivalent to [OpenInRoot], except that the root is provided
|
||||
// using an *[os.File] handle, to ensure that the correct root directory is used.
|
||||
func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) {
|
||||
handle, err := completeLookupInRoot(root, unsafePath)
|
||||
if err != nil {
|
||||
return nil, &os.PathError{Op: "securejoin.OpenInRoot", Path: unsafePath, Err: err}
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
102
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go
generated
vendored
102
vendor/github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gopathrs/openat2_linux.go
generated
vendored
@@ -1,102 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
|
||||
// Copyright (C) 2024-2025 SUSE LLC
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package gopathrs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/procfs"
|
||||
)
|
||||
|
||||
func openat2(dir fd.Fd, path string, how *unix.OpenHow) (*os.File, error) {
|
||||
file, err := fd.Openat2(dir, path, how)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If we are using RESOLVE_IN_ROOT, the name we generated may be wrong.
|
||||
if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT {
|
||||
if actualPath, err := procfs.ProcSelfFdReadlink(file); err == nil {
|
||||
// TODO: Ideally we would not need to dup the fd, but you cannot
|
||||
// easily just swap an *os.File with one from the same fd
|
||||
// (the GC will close the old one, and you cannot clear the
|
||||
// finaliser easily because it is associated with an internal
|
||||
// field of *os.File not *os.File itself).
|
||||
newFile, err := fd.DupWithName(file, actualPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = file.Close()
|
||||
file = newFile
|
||||
}
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func lookupOpenat2(root fd.Fd, unsafePath string, partial bool) (*os.File, string, error) {
|
||||
if !partial {
|
||||
file, err := openat2(root, unsafePath, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
|
||||
})
|
||||
return file, "", err
|
||||
}
|
||||
return partialLookupOpenat2(root, unsafePath)
|
||||
}
|
||||
|
||||
// partialLookupOpenat2 is an alternative implementation of
|
||||
// partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a
|
||||
// handle to the deepest existing child of the requested path within the root.
|
||||
func partialLookupOpenat2(root fd.Fd, unsafePath string) (*os.File, string, error) {
|
||||
// TODO: Implement this as a git-bisect-like binary search.
|
||||
|
||||
unsafePath = filepath.ToSlash(unsafePath) // noop
|
||||
endIdx := len(unsafePath)
|
||||
var lastError error
|
||||
for endIdx > 0 {
|
||||
subpath := unsafePath[:endIdx]
|
||||
|
||||
handle, err := openat2(root, subpath, &unix.OpenHow{
|
||||
Flags: unix.O_PATH | unix.O_CLOEXEC,
|
||||
Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
|
||||
})
|
||||
if err == nil {
|
||||
// Jump over the slash if we have a non-"" remainingPath.
|
||||
if endIdx < len(unsafePath) {
|
||||
endIdx++
|
||||
}
|
||||
// We found a subpath!
|
||||
return handle, unsafePath[endIdx:], lastError
|
||||
}
|
||||
if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) {
|
||||
// That path doesn't exist, let's try the next directory up.
|
||||
endIdx = strings.LastIndexByte(subpath, '/')
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
return nil, "", fmt.Errorf("open subpath: %w", err)
|
||||
}
|
||||
// If we couldn't open anything, the whole subpath is missing. Return a
|
||||
// copy of the root fd so that the caller doesn't close this one by
|
||||
// accident.
|
||||
rootClone, err := fd.Dup(root)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return rootClone, unsafePath, lastError
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Copyright (C) 2022 The Go Authors. All rights reserved.
|
||||
// Copyright (C) 2025 SUSE LLC. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.BSD file.
|
||||
|
||||
// The parsing logic is very loosely based on the Go stdlib's
|
||||
// src/internal/syscall/unix/kernel_version_linux.go but with an API that looks
|
||||
// a bit like runc's libcontainer/system/kernelversion.
|
||||
//
|
||||
// TODO(cyphar): This API has been copied around to a lot of different projects
|
||||
// (Docker, containerd, runc, and now filepath-securejoin) -- maybe we should
|
||||
// put it in a separate project?
|
||||
|
||||
// Package kernelversion provides a simple mechanism for checking whether the
|
||||
// running kernel is at least as new as some baseline kernel version. This is
|
||||
// often useful when checking for features that would be too complicated to
|
||||
// test support for (or in cases where we know that some kernel features in
|
||||
// backport-heavy kernels are broken and need to be avoided).
|
||||
package kernelversion
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
|
||||
)
|
||||
|
||||
// KernelVersion is a numeric representation of the key numerical elements of a
|
||||
// kernel version (for instance, "4.1.2-default-1" would be represented as
|
||||
// KernelVersion{4, 1, 2}).
|
||||
type KernelVersion []uint64
|
||||
|
||||
func (kver KernelVersion) String() string {
|
||||
var str strings.Builder
|
||||
for idx, elem := range kver {
|
||||
if idx != 0 {
|
||||
_, _ = str.WriteRune('.')
|
||||
}
|
||||
_, _ = str.WriteString(strconv.FormatUint(elem, 10))
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
var errInvalidKernelVersion = errors.New("invalid kernel version")
|
||||
|
||||
// parseKernelVersion parses a string and creates a KernelVersion based on it.
|
||||
func parseKernelVersion(kverStr string) (KernelVersion, error) {
|
||||
kver := make(KernelVersion, 1, 3)
|
||||
for idx, ch := range kverStr {
|
||||
if '0' <= ch && ch <= '9' {
|
||||
v := &kver[len(kver)-1]
|
||||
*v = (*v * 10) + uint64(ch-'0')
|
||||
} else {
|
||||
if idx == 0 || kverStr[idx-1] < '0' || '9' < kverStr[idx-1] {
|
||||
// "." must be preceded by a digit while in version section
|
||||
return nil, fmt.Errorf("%w %q: kernel version has dot(s) followed by non-digit in version section", errInvalidKernelVersion, kverStr)
|
||||
}
|
||||
if ch != '.' {
|
||||
break
|
||||
}
|
||||
kver = append(kver, 0)
|
||||
}
|
||||
}
|
||||
if len(kver) < 2 {
|
||||
return nil, fmt.Errorf("%w %q: kernel versions must contain at least two components", errInvalidKernelVersion, kverStr)
|
||||
}
|
||||
return kver, nil
|
||||
}
|
||||
|
||||
// getKernelVersion gets the current kernel version.
|
||||
var getKernelVersion = gocompat.SyncOnceValues(func() (KernelVersion, error) {
|
||||
var uts unix.Utsname
|
||||
if err := unix.Uname(&uts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Remove the \x00 from the release.
|
||||
release := uts.Release[:]
|
||||
return parseKernelVersion(string(release[:bytes.IndexByte(release, 0)]))
|
||||
})
|
||||
|
||||
// GreaterEqualThan returns true if the the host kernel version is greater than
|
||||
// or equal to the provided [KernelVersion]. When doing this comparison, any
|
||||
// non-numerical suffixes of the host kernel version are ignored.
|
||||
//
|
||||
// If the number of components provided is not equal to the number of numerical
|
||||
// components of the host kernel version, any missing components are treated as
|
||||
// 0. This means that GreaterEqualThan(KernelVersion{4}) will be treated the
|
||||
// same as GreaterEqualThan(KernelVersion{4, 0, 0, ..., 0, 0}), and that if the
|
||||
// host kernel version is "4" then GreaterEqualThan(KernelVersion{4, 1}) will
|
||||
// return false (because the host version will be treated as "4.0").
|
||||
func GreaterEqualThan(wantKver KernelVersion) (bool, error) {
|
||||
hostKver, err := getKernelVersion()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Pad out the kernel version lengths to match one another.
|
||||
cmpLen := gocompat.Max2(len(hostKver), len(wantKver))
|
||||
hostKver = append(hostKver, make(KernelVersion, cmpLen-len(hostKver))...)
|
||||
wantKver = append(wantKver, make(KernelVersion, cmpLen-len(wantKver))...)
|
||||
|
||||
for i := 0; i < cmpLen; i++ {
|
||||
switch gocompat.CmpCompare(hostKver[i], wantKver[i]) {
|
||||
case -1:
|
||||
// host < want
|
||||
return false, nil
|
||||
case +1:
|
||||
// host > want
|
||||
return true, nil
|
||||
case 0:
|
||||
continue
|
||||
}
|
||||
}
|
||||
// equal version values
|
||||
return true, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user