Skip to content

Commit

Permalink
Add optional digest suffix to template locator
Browse files Browse the repository at this point in the history
The suffix is "@digest" which may include an optional algorithm
(defaults to "sha256"). The encoded digest must be at least 7
characters long.

Examples:
- template://my@sha256:60a87371451eabcd211c929759db61746a7c6a1c068f59d868db6aa8dca637bd
- template://my@sha256:60a87371451
- template://my@60a8737

Signed-off-by: Jan Dubois <[email protected]>
  • Loading branch information
jandubois committed Dec 14, 2024
1 parent 138f55c commit 71bf68f
Showing 1 changed file with 59 additions and 15 deletions.
74 changes: 59 additions & 15 deletions pkg/limatmpl/locator.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,70 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"strings"

"github.com/containerd/containerd/identifiers"
"github.com/lima-vm/lima/pkg/ioutilx"
"github.com/lima-vm/lima/pkg/templatestore"
"github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
)

type Template struct {
Name string
Locator string
Bytes []byte

algorithm digest.Algorithm
digest string
}

const yBytesLimit = 4 * 1024 * 1024 // 4MiB

// Only sha256, sha384, and sha512 are actually available but we reserve all lowercase and digit strings.
// Note that only lowercase hex digits are accepted.
var digestSuffixRegex = regexp.MustCompile(`^(.+)@(?:([a-z0-9]+):)?([a-f0-9]+)$`)

// splitOffDigest splits off an optional @algorithm:digest suffix from the locator.
func (tmpl *Template) splitOffDigest() error {
matches := digestSuffixRegex.FindStringSubmatch(tmpl.Locator)
if matches != nil {
tmpl.algorithm = digest.Algorithm(matches[2])
if tmpl.algorithm == "" {
tmpl.algorithm = digest.SHA256
}
if !tmpl.algorithm.Available() {
return fmt.Errorf("locator %q uses unavailable digest algorithm", tmpl.Locator)
}
tmpl.digest = matches[3]
if len(tmpl.digest) < 7 {
return fmt.Errorf("locator %q digest has fewer than 7 hex digits", tmpl.Locator)
}
tmpl.Locator = matches[1]
}
return nil
}

// Read fetches the content pointed at by a template locator. If the locator has an optional
// digest suffix, then the digest must match, or Read will return an error.
func Read(ctx context.Context, name, locator string) (*Template, error) {
var err error

tmpl := &Template{
Name: name,
Locator: locator,
}
if err = tmpl.splitOffDigest(); err != nil {
return nil, err
}

isTemplateURL, templateURL := SeemsTemplateURL(locator)
isTemplateURL, templateURL := SeemsTemplateURL(tmpl.Locator)
switch {
case isTemplateURL:
// No need to use SecureJoin here. https://github.com/lima-vm/lima/pull/805#discussion_r853411702
templateName := filepath.Join(templateURL.Host, templateURL.Path)
logrus.Debugf("interpreting argument %q as a template name %q", locator, templateName)
logrus.Debugf("interpreting argument %q as a template name %q", tmpl.Locator, templateName)
if tmpl.Name == "" {
// e.g., templateName = "deprecated/centos-7" , tmpl.Name = "centos-7"
tmpl.Name = filepath.Base(templateName)
Expand All @@ -47,15 +81,15 @@ func Read(ctx context.Context, name, locator string) (*Template, error) {
if err != nil {
return nil, err
}
case SeemsHTTPURL(locator):
case SeemsHTTPURL(tmpl.Locator):
if tmpl.Name == "" {
tmpl.Name, err = InstNameFromURL(locator)
tmpl.Name, err = InstNameFromURL(tmpl.Locator)
if err != nil {
return nil, err
}
}
logrus.Debugf("interpreting argument %q as a http url for instance %q", locator, tmpl.Name)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, locator, http.NoBody)
logrus.Debugf("interpreting argument %q as a http url for instance %q", tmpl.Locator, tmpl.Name)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, tmpl.Locator, http.NoBody)
if err != nil {
return nil, err
}
Expand All @@ -68,15 +102,15 @@ func Read(ctx context.Context, name, locator string) (*Template, error) {
if err != nil {
return nil, err
}
case SeemsFileURL(locator):
case SeemsFileURL(tmpl.Locator):
if tmpl.Name == "" {
tmpl.Name, err = InstNameFromURL(locator)
tmpl.Name, err = InstNameFromURL(tmpl.Locator)
if err != nil {
return nil, err
}
}
logrus.Debugf("interpreting argument %q as a file url for instance %q", locator, tmpl.Name)
r, err := os.Open(strings.TrimPrefix(locator, "file://"))
logrus.Debugf("interpreting argument %q as a file url for instance %q", tmpl.Locator, tmpl.Name)
r, err := os.Open(strings.TrimPrefix(tmpl.Locator, "file://"))
if err != nil {
return nil, err
}
Expand All @@ -85,15 +119,15 @@ func Read(ctx context.Context, name, locator string) (*Template, error) {
if err != nil {
return nil, err
}
case SeemsYAMLPath(locator):
case SeemsYAMLPath(tmpl.Locator):
if tmpl.Name == "" {
tmpl.Name, err = InstNameFromYAMLPath(locator)
tmpl.Name, err = InstNameFromYAMLPath(tmpl.Locator)
if err != nil {
return nil, err
}
}
logrus.Debugf("interpreting argument %q as a file path for instance %q", locator, tmpl.Name)
r, err := os.Open(locator)
logrus.Debugf("interpreting argument %q as a file path for instance %q", tmpl.Locator, tmpl.Name)
r, err := os.Open(tmpl.Locator)
if err != nil {
return nil, err
}
Expand All @@ -102,12 +136,22 @@ func Read(ctx context.Context, name, locator string) (*Template, error) {
if err != nil {
return nil, err
}
case locator == "-":
case tmpl.Locator == "-":
tmpl.Bytes, err = io.ReadAll(os.Stdin)
if err != nil {
return nil, fmt.Errorf("unexpected error reading stdin: %w", err)
}
}

if tmpl.digest != "" {
actualDigest := digest.Algorithm(tmpl.algorithm).FromBytes(tmpl.Bytes).Encoded()
if len(tmpl.digest) < len(actualDigest) {
actualDigest = actualDigest[:len(tmpl.digest)]
}
if actualDigest != tmpl.digest {
return nil, fmt.Errorf("locator %q digest doesn't match content digest %q", locator, actualDigest)
}
}
return tmpl, nil
}

Expand Down

0 comments on commit 71bf68f

Please sign in to comment.