github.com/ahmet2mir/goreleaser@v0.180.3-0.20210927151101-8e5ee5a9b8c5/internal/pipe/sign/sign.go (about)

     1  package sign
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/apex/log"
    13  	"github.com/goreleaser/goreleaser/internal/artifact"
    14  	"github.com/goreleaser/goreleaser/internal/gio"
    15  	"github.com/goreleaser/goreleaser/internal/ids"
    16  	"github.com/goreleaser/goreleaser/internal/logext"
    17  	"github.com/goreleaser/goreleaser/internal/pipe"
    18  	"github.com/goreleaser/goreleaser/internal/semerrgroup"
    19  	"github.com/goreleaser/goreleaser/internal/tmpl"
    20  	"github.com/goreleaser/goreleaser/pkg/config"
    21  	"github.com/goreleaser/goreleaser/pkg/context"
    22  )
    23  
    24  // Pipe that signs common artifacts.
    25  type Pipe struct{}
    26  
    27  func (Pipe) String() string                 { return "signing artifacts" }
    28  func (Pipe) Skip(ctx *context.Context) bool { return ctx.SkipSign || len(ctx.Config.Signs) == 0 }
    29  
    30  // Default sets the Pipes defaults.
    31  func (Pipe) Default(ctx *context.Context) error {
    32  	ids := ids.New("signs")
    33  	for i := range ctx.Config.Signs {
    34  		cfg := &ctx.Config.Signs[i]
    35  		if cfg.Cmd == "" {
    36  			cfg.Cmd = "gpg"
    37  		}
    38  		if cfg.Signature == "" {
    39  			cfg.Signature = "${artifact}.sig"
    40  		}
    41  		if len(cfg.Args) == 0 {
    42  			cfg.Args = []string{"--output", "$signature", "--detach-sig", "$artifact"}
    43  		}
    44  		if cfg.Artifacts == "" {
    45  			cfg.Artifacts = "none"
    46  		}
    47  		if cfg.ID == "" {
    48  			cfg.ID = "default"
    49  		}
    50  		ids.Inc(cfg.ID)
    51  	}
    52  	return ids.Validate()
    53  }
    54  
    55  // Run executes the Pipe.
    56  func (Pipe) Run(ctx *context.Context) error {
    57  	g := semerrgroup.New(ctx.Parallelism)
    58  	for i := range ctx.Config.Signs {
    59  		cfg := ctx.Config.Signs[i]
    60  		g.Go(func() error {
    61  			var filters []artifact.Filter
    62  			switch cfg.Artifacts {
    63  			case "checksum":
    64  				filters = append(filters, artifact.ByType(artifact.Checksum))
    65  				if len(cfg.IDs) > 0 {
    66  					log.Warn("when artifacts is `checksum`, `ids` has no effect. ignoring")
    67  				}
    68  			case "source":
    69  				filters = append(filters, artifact.ByType(artifact.UploadableSourceArchive))
    70  				if len(cfg.IDs) > 0 {
    71  					log.Warn("when artifacts is `source`, `ids` has no effect. ignoring")
    72  				}
    73  			case "all":
    74  				filters = append(filters, artifact.Or(
    75  					artifact.ByType(artifact.UploadableArchive),
    76  					artifact.ByType(artifact.UploadableBinary),
    77  					artifact.ByType(artifact.UploadableSourceArchive),
    78  					artifact.ByType(artifact.Checksum),
    79  					artifact.ByType(artifact.LinuxPackage),
    80  				))
    81  			case "archive":
    82  				filters = append(filters, artifact.ByType(artifact.UploadableArchive))
    83  			case "binary":
    84  				filters = append(filters, artifact.ByType(artifact.UploadableBinary))
    85  			case "package":
    86  				filters = append(filters, artifact.ByType(artifact.LinuxPackage))
    87  			case "none": // TODO(caarlos0): this is not very useful, lets remove it.
    88  				return pipe.ErrSkipSignEnabled
    89  			default:
    90  				return fmt.Errorf("invalid list of artifacts to sign: %s", cfg.Artifacts)
    91  			}
    92  
    93  			if len(cfg.IDs) > 0 {
    94  				filters = append(filters, artifact.ByIDs(cfg.IDs...))
    95  			}
    96  			return sign(ctx, cfg, ctx.Artifacts.Filter(artifact.And(filters...)).List())
    97  		})
    98  	}
    99  	return g.Wait()
   100  }
   101  
   102  func sign(ctx *context.Context, cfg config.Sign, artifacts []*artifact.Artifact) error {
   103  	for _, a := range artifacts {
   104  		artifact, err := signone(ctx, cfg, a)
   105  		if err != nil {
   106  			return err
   107  		}
   108  		if artifact != nil {
   109  			ctx.Artifacts.Add(artifact)
   110  		}
   111  	}
   112  	return nil
   113  }
   114  
   115  func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*artifact.Artifact, error) {
   116  	env := ctx.Env.Copy()
   117  	env["artifact"] = a.Path
   118  	env["artifactID"] = a.ExtraOr("ID", "").(string)
   119  
   120  	name, err := tmpl.New(ctx).WithEnv(env).Apply(expand(cfg.Signature, env))
   121  	if err != nil {
   122  		return nil, fmt.Errorf("sign failed: %s: invalid template: %w", a, err)
   123  	}
   124  	env["signature"] = name
   125  
   126  	// nolint:prealloc
   127  	var args []string
   128  	for _, a := range cfg.Args {
   129  		arg, err := tmpl.New(ctx).WithEnv(env).Apply(expand(a, env))
   130  		if err != nil {
   131  			return nil, fmt.Errorf("sign failed: %s: invalid template: %w", a, err)
   132  		}
   133  		args = append(args, arg)
   134  	}
   135  
   136  	var stdin io.Reader
   137  	if cfg.Stdin != nil {
   138  		s, err := tmpl.New(ctx).WithEnv(env).Apply(expand(*cfg.Stdin, env))
   139  		if err != nil {
   140  			return nil, err
   141  		}
   142  		stdin = strings.NewReader(s)
   143  	} else if cfg.StdinFile != "" {
   144  		f, err := os.Open(cfg.StdinFile)
   145  		if err != nil {
   146  			return nil, fmt.Errorf("sign failed: cannot open file %s: %w", cfg.StdinFile, err)
   147  		}
   148  		defer f.Close()
   149  
   150  		stdin = f
   151  	}
   152  
   153  	fields := log.Fields{"cmd": cfg.Cmd, "artifact": a.Name}
   154  
   155  	// The GoASTScanner flags this as a security risk.
   156  	// However, this works as intended. The nosec annotation
   157  	// tells the scanner to ignore this.
   158  	// #nosec
   159  	cmd := exec.CommandContext(ctx, cfg.Cmd, args...)
   160  	var b bytes.Buffer
   161  	w := gio.Safe(&b)
   162  	cmd.Stderr = io.MultiWriter(logext.NewWriter(fields, logext.Error), w)
   163  	cmd.Stdout = io.MultiWriter(logext.NewWriter(fields, logext.Info), w)
   164  	if stdin != nil {
   165  		cmd.Stdin = stdin
   166  	}
   167  	log.WithFields(fields).Info("signing")
   168  	if err := cmd.Run(); err != nil {
   169  		return nil, fmt.Errorf("sign: %s failed: %w: %s", cfg.Cmd, err, b.String())
   170  	}
   171  
   172  	if cfg.Signature == "" {
   173  		return nil, nil
   174  	}
   175  
   176  	env["artifact"] = a.Name
   177  	name, err = tmpl.New(ctx).WithEnv(env).Apply(expand(cfg.Signature, env))
   178  	if err != nil {
   179  		return nil, fmt.Errorf("sign failed: %s: invalid template: %w", a, err)
   180  	}
   181  
   182  	artifactPathBase, _ := filepath.Split(a.Path)
   183  	sigFilename := filepath.Base(env["signature"])
   184  	return &artifact.Artifact{
   185  		Type: artifact.Signature,
   186  		Name: name,
   187  		Path: filepath.Join(artifactPathBase, sigFilename),
   188  		Extra: map[string]interface{}{
   189  			"ID": cfg.ID,
   190  		},
   191  	}, nil
   192  }
   193  
   194  func expand(s string, env map[string]string) string {
   195  	return os.Expand(s, func(key string) string {
   196  		return env[key]
   197  	})
   198  }