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 }