Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions pkg/app/commands.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package app

import (
"github.com/fastly/kingpin"

"github.com/fastly/cli/pkg/cmd"
"github.com/fastly/cli/pkg/commands/acl"
"github.com/fastly/cli/pkg/commands/aclentry"
Expand Down Expand Up @@ -56,8 +58,6 @@ import (
"github.com/fastly/cli/pkg/commands/serviceversion"
"github.com/fastly/cli/pkg/commands/shellcomplete"
"github.com/fastly/cli/pkg/commands/stats"
"github.com/fastly/cli/pkg/global"

tlsConfig "github.com/fastly/cli/pkg/commands/tls/config"
tlsCustom "github.com/fastly/cli/pkg/commands/tls/custom"
tlsCustomActivation "github.com/fastly/cli/pkg/commands/tls/custom/activation"
Expand All @@ -73,8 +73,8 @@ import (
"github.com/fastly/cli/pkg/commands/vcl/snippet"
"github.com/fastly/cli/pkg/commands/version"
"github.com/fastly/cli/pkg/commands/whoami"
"github.com/fastly/cli/pkg/global"
"github.com/fastly/cli/pkg/manifest"
"github.com/fastly/kingpin"
)

func defineCommands(
Expand Down Expand Up @@ -110,6 +110,7 @@ func defineCommands(
computeCmdRoot := compute.NewRootCommand(app, g)
computeBuild := compute.NewBuildCommand(computeCmdRoot.CmdClause, g, m)
computeDeploy := compute.NewDeployCommand(computeCmdRoot.CmdClause, g, m)
computeHashFiles := compute.NewHashFilesCommand(computeCmdRoot.CmdClause, g, computeBuild, m)
computeHashsum := compute.NewHashsumCommand(computeCmdRoot.CmdClause, g, computeBuild, m)
computeInit := compute.NewInitCommand(computeCmdRoot.CmdClause, g, m)
computePack := compute.NewPackCommand(computeCmdRoot.CmdClause, g, m)
Expand Down Expand Up @@ -461,6 +462,7 @@ func defineCommands(
computeBuild,
computeCmdRoot,
computeDeploy,
computeHashFiles,
computeHashsum,
computeInit,
computePack,
Expand Down
6 changes: 1 addition & 5 deletions pkg/commands/compute/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ const (
trialNotActivated = "Valid values for 'type' are: 'vcl'"
)

// PackageSizeLimit describes the package size limit in bytes (currently 50mb)
// https://docs.fastly.com/products/compute-at-edge-billing-and-resource-limits#resource-limits
var PackageSizeLimit int64 = 50000000

// DeployCommand deploys an artifact previously produced by build.
type DeployCommand struct {
cmd.Base
Expand Down Expand Up @@ -301,7 +297,7 @@ func validatePackage(
}
}

if pkgSize > PackageSizeLimit {
if pkgSize > MaxPackageSize {
return pkgPath, hashSum, fsterr.RemediationError{
Inner: fmt.Errorf("package size is too large (%d bytes)", pkgSize),
Remediation: fsterr.PackageSizeRemediation,
Expand Down
9 changes: 5 additions & 4 deletions pkg/commands/compute/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import (
"testing"
"time"

"github.com/fastly/go-fastly/v8/fastly"

"github.com/fastly/cli/pkg/app"
"github.com/fastly/cli/pkg/commands/compute"
"github.com/fastly/cli/pkg/errors"
"github.com/fastly/cli/pkg/manifest"
"github.com/fastly/cli/pkg/mock"
"github.com/fastly/cli/pkg/testutil"
"github.com/fastly/cli/pkg/threadsafe"
"github.com/fastly/go-fastly/v8/fastly"
)

// NOTE: Some tests don't provide a Service ID via any mechanism (e.g. flag
Expand Down Expand Up @@ -77,7 +78,7 @@ func TestDeploy(t *testing.T) {
}
defer os.Chdir(pwd)

originalPackageSizeLimit := compute.PackageSizeLimit
originalPackageSizeLimit := compute.MaxPackageSize
args := testutil.Args
scenarios := []struct {
api mock.API
Expand Down Expand Up @@ -1846,11 +1847,11 @@ func TestDeploy(t *testing.T) {
}

if testcase.reduceSizeLimit {
compute.PackageSizeLimit = 1000000 // 1mb (our test package should above this)
compute.MaxPackageSize = 1000000 // 1mb (our test package should above this)
} else {
// As multiple test scenarios run within a single environment instance
// we need to ensure each scenario resets the package variable.
compute.PackageSizeLimit = originalPackageSizeLimit
compute.MaxPackageSize = originalPackageSizeLimit
}

if len(testcase.stdin) > 1 {
Expand Down
158 changes: 158 additions & 0 deletions pkg/commands/compute/hashfiles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package compute

import (
"archive/tar"
"bytes"
"compress/gzip"
"crypto/sha512"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"sort"

"github.com/kennygrant/sanitize"

"github.com/fastly/cli/pkg/cmd"
"github.com/fastly/cli/pkg/global"
"github.com/fastly/cli/pkg/manifest"
"github.com/fastly/cli/pkg/text"
)

// NOTE: This is variable not a constant for the sake of test manipulations.
// https://developer.fastly.com/learning/compute/#limitations-and-constraints
var MaxPackageSize int64 = 100000000 // 100MB in bytes

// HashFilesCommand produces a deployable artifact from files on the local disk.
type HashFilesCommand struct {
cmd.Base

buildCmd *BuildCommand
Manifest manifest.Data
Package string
SkipBuild bool
}

// NewHashFilesCommand returns a usable command registered under the parent.
func NewHashFilesCommand(parent cmd.Registerer, g *global.Data, build *BuildCommand, m manifest.Data) *HashFilesCommand {
var c HashFilesCommand
c.buildCmd = build
c.Globals = g
c.Manifest = m
c.CmdClause = parent.Command("hash-files", "Generate a SHA512 digest from the contents of the Compute@Edge package")
c.CmdClause.Flag("package", "Path to a package tar.gz").Short('p').StringVar(&c.Package)
c.CmdClause.Flag("skip-build", "Skip the build step").BoolVar(&c.SkipBuild)
return &c
}

// Exec implements the command interface.
func (c *HashFilesCommand) Exec(in io.Reader, out io.Writer) (err error) {
if !c.SkipBuild {
err = c.Build(in, out)
if err != nil {
return err
}
}

pkgName := fmt.Sprintf("%s.tar.gz", sanitize.BaseName(c.Globals.Manifest.File.Name))
pkg := filepath.Join("pkg", pkgName)

if c.Package != "" {
pkg, err = filepath.Abs(c.Package)
if err != nil {
return fmt.Errorf("failed to locate package path '%s': %w", c.Package, err)
}
}

var r io.Reader
// G304 (CWE-22): Potential file inclusion via variable
// #nosec
r, err = os.Open(pkg)
if err != nil {
return fmt.Errorf("failed to open package '%s': %w", pkg, err)
}

zr, err := gzip.NewReader(r)
if err != nil {
return fmt.Errorf("failed to create a gzip reader: %w", err)
}

files, err := c.ReadFilesFromPackage(tar.NewReader(zr))
if err != nil {
return fmt.Errorf("failed to read files within the package: %w", err)
}

hash, err := c.GetFilesHash(files)
if err != nil {
return fmt.Errorf("failed to generate hash from package files: %w", err)
}

text.Output(out, hash)
return nil
}

// Build constructs and executes the build logic.
func (c *HashFilesCommand) Build(in io.Reader, out io.Writer) error {
output := out
if !c.Globals.Verbose() {
output = io.Discard
}
return c.buildCmd.Exec(in, output)
}

// ReadFilesFromPackage reads all files within the provided package tar and
// generates a map data structure where the key is the filename and the value is
// the file contents.
func (c *HashFilesCommand) ReadFilesFromPackage(tr *tar.Reader) (map[string]*bytes.Buffer, error) {
// Store the content of every file within the package.
contents := make(map[string]*bytes.Buffer)

// Track overall package size.
var pkgSize int64

for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}

// Avoids G110: Potential DoS vulnerability via decompression bomb (gosec).
pkgSize += hdr.Size
if pkgSize > MaxPackageSize {
return nil, errors.New("package size exceeded 100MB limit")
}

if hdr.Typeflag != tar.TypeReg {
continue
}

contents[hdr.Name] = &bytes.Buffer{}

_, err = io.CopyN(contents[hdr.Name], tr, hdr.Size)
if err != nil {
return nil, err
}
}

return contents, nil
}

// GetFilesHash returns a hash of all the filecontent in sorted filename order.
func (c *HashFilesCommand) GetFilesHash(contents map[string]*bytes.Buffer) (string, error) {
keys := make([]string, 0, len(contents))
for k := range contents {
keys = append(keys, k)
}
sort.Strings(keys)
h := sha512.New()
for _, fname := range keys {
if _, err := io.Copy(h, contents[fname]); err != nil {
return "", err
}
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
8 changes: 7 additions & 1 deletion pkg/commands/compute/hashsum.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,25 @@ type HashsumCommand struct {
}

// NewHashsumCommand returns a usable command registered under the parent.
// Deprecated: Use NewHashFilesCommand instead.
func NewHashsumCommand(parent cmd.Registerer, g *global.Data, build *BuildCommand, m manifest.Data) *HashsumCommand {
var c HashsumCommand
c.buildCmd = build
c.Globals = g
c.Manifest = m
c.CmdClause = parent.Command("hashsum", "Generate a SHA512 digest from a Compute@Edge package")
c.CmdClause = parent.Command("hashsum", "Generate a SHA512 digest from a Compute@Edge package").Hidden()
c.CmdClause.Flag("package", "Path to a package tar.gz").Short('p').StringVar(&c.Package)
c.CmdClause.Flag("skip-build", "Skip the build step").BoolVar(&c.SkipBuild)
return &c
}

// Exec implements the command interface.
func (c *HashsumCommand) Exec(in io.Reader, out io.Writer) (err error) {
if !c.Globals.Flags.Quiet {
text.Warning(out, "This command is deprecated. Use `fastly compute hash-files` instead.")
text.Break(out)
}

Comment on lines +39 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if !c.SkipBuild {
err = c.Build(in, out)
if err != nil {
Expand Down
32 changes: 21 additions & 11 deletions pkg/manifest/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,27 @@ import (
// manifest file schema.
type File struct {
// Args is necessary to track the subcommand called (see: File.Read method).
Args []string `toml:"-"`
Authors []string `toml:"authors"`
Description string `toml:"description"`
Language string `toml:"language"`
Profile string `toml:"profile,omitempty"`
LocalServer LocalServer `toml:"local_server,omitempty"`
ManifestVersion Version `toml:"manifest_version"`
Name string `toml:"name"`
Scripts Scripts `toml:"scripts,omitempty"`
ServiceID string `toml:"service_id"`
Setup Setup `toml:"setup,omitempty"`
Args []string `toml:"-"`
// Authors is a list of project authors (typically an email).
Authors []string `toml:"authors"`
// Description is the project description.
Description string `toml:"description"`
// Language is the programming language used for the project.
Language string `toml:"language"`
// Profile is the name of the profile account the Fastly CLI should use to make API requests.
Profile string `toml:"profile,omitempty"`
// LocalServer describes the configuration for the local server built into the Fastly CLI.
LocalServer LocalServer `toml:"local_server,omitempty"`
// ManifestVersion is the manifest schema version number.
ManifestVersion Version `toml:"manifest_version"`
// Name is the package name.
Name string `toml:"name"`
// Scripts describes customisation options for the Fastly CLI build step.
Scripts Scripts `toml:"scripts,omitempty"`
// ServiceID is the Fastly Service ID to deploy the package to.
ServiceID string `toml:"service_id"`
// Setup describes a set of service configuration that works with the code in the package.
Setup Setup `toml:"setup,omitempty"`

quiet bool
errLog fsterr.LogInterface
Expand Down