1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-17 20:47:50 +02:00

fix: move certificates setting to pipelines

This commit is contained in:
Pablo Lalloni 2018-09-08 16:33:46 -03:00 committed by Carlos Alexandro Becker
parent c6a3686070
commit 4728741b29
5 changed files with 257 additions and 120 deletions

View File

@ -9,7 +9,6 @@ import (
"html/template"
"io"
h "net/http"
"net/url"
"os"
"strings"
@ -99,7 +98,7 @@ func CheckConfig(ctx *context.Context, put *config.Put, kind string) error {
return misconfigured(kind, put, fmt.Sprintf("missing %s environment variable", envName))
}
if ctx.Config.TrustedCerts != "" && !x509.NewCertPool().AppendCertsFromPEM([]byte(ctx.Config.TrustedCerts)) {
if put.TrustedCerts != "" && !x509.NewCertPool().AppendCertsFromPEM([]byte(put.TrustedCerts)) {
return misconfigured(kind, put, "no certificate could be added from the specified trusted_certificates configuration")
}
@ -149,7 +148,7 @@ func Upload(ctx *context.Context, puts []config.Put, kind string, check Response
}).Error(err.Error())
return err
}
if err := uploadWithFilter(ctx, put, artifact.Or(filters...), kind, check); err != nil {
if err := uploadWithFilter(ctx, &put, artifact.Or(filters...), kind, check); err != nil {
return err
}
}
@ -157,7 +156,7 @@ func Upload(ctx *context.Context, puts []config.Put, kind string, check Response
return nil
}
func uploadWithFilter(ctx *context.Context, put config.Put, filter artifact.Filter, kind string, check ResponseChecker) error {
func uploadWithFilter(ctx *context.Context, put *config.Put, filter artifact.Filter, kind string, check ResponseChecker) error {
var g = semerrgroup.New(ctx.Parallelism)
for _, artifact := range ctx.Artifacts.Filter(filter).List() {
artifact := artifact
@ -169,7 +168,7 @@ func uploadWithFilter(ctx *context.Context, put config.Put, filter artifact.Filt
}
// uploadAsset uploads file to target and logs all actions
func uploadAsset(ctx *context.Context, put config.Put, artifact artifact.Artifact, kind string, check ResponseChecker) error {
func uploadAsset(ctx *context.Context, put *config.Put, artifact artifact.Artifact, kind string, check ResponseChecker) error {
envBase := fmt.Sprintf("%s_%s_", strings.ToUpper(kind), strings.ToUpper(put.Name))
username := put.Username
if username == "" {
@ -199,7 +198,7 @@ func uploadAsset(ctx *context.Context, put config.Put, artifact artifact.Artifac
}
targetURL += artifact.Name
_, err = uploadAssetToServer(ctx, targetURL, username, secret, asset, check)
_, err = uploadAssetToServer(ctx, put, targetURL, username, secret, asset, check)
if err != nil {
msg := fmt.Sprintf("%s: upload failed", kind)
log.WithError(err).WithFields(log.Fields{
@ -218,22 +217,18 @@ func uploadAsset(ctx *context.Context, put config.Put, artifact artifact.Artifac
}
// uploadAssetToServer uploads the asset file to target
func uploadAssetToServer(ctx *context.Context, target, username, secret string, a *asset, check ResponseChecker) (*h.Response, error) {
func uploadAssetToServer(ctx *context.Context, put *config.Put, target, username, secret string, a *asset, check ResponseChecker) (*h.Response, error) {
req, err := newUploadRequest(target, username, secret, a)
if err != nil {
return nil, err
}
return executeHTTPRequest(ctx, req, check)
return executeHTTPRequest(ctx, put, req, check)
}
// newUploadRequest creates a new h.Request for uploading
func newUploadRequest(target, username, secret string, a *asset) (*h.Request, error) {
u, err := url.Parse(target)
if err != nil {
return nil, err
}
req, err := h.NewRequest("PUT", u.String(), a.ReadCloser)
req, err := h.NewRequest("PUT", target, a.ReadCloser)
if err != nil {
return nil, err
}
@ -244,22 +239,29 @@ func newUploadRequest(target, username, secret string, a *asset) (*h.Request, er
return req, err
}
// executeHTTPRequest processes the http call with respect of context ctx
func executeHTTPRequest(ctx *context.Context, req *h.Request, check ResponseChecker) (*h.Response, error) {
client := h.DefaultClient
if ctx.Config.TrustedCerts != "" {
pool, err := loadSystemRoots()
if err != nil {
return "", nil, err
}
pool.AppendCertsFromPEM([]byte(ctx.Config.TrustedCerts)) // already validated certs checked by CheckConfig
client = &h.Client{
Transport: &h.Transport{
TLSClientConfig: &tls.Config{
RootCAs: pool,
},
func getHTTPClient(put *config.Put) (*h.Client, error) {
if put.TrustedCerts == "" {
return h.DefaultClient, nil
}
pool, err := loadSystemRoots()
if err != nil {
return nil, err
}
pool.AppendCertsFromPEM([]byte(put.TrustedCerts)) // already validated certs checked by CheckConfig
return &h.Client{
Transport: &h.Transport{
TLSClientConfig: &tls.Config{
RootCAs: pool,
},
}
},
}, nil
}
// executeHTTPRequest processes the http call with respect of context ctx
func executeHTTPRequest(ctx *context.Context, put *config.Put, req *h.Request, check ResponseChecker) (*h.Response, error) {
client, err := getHTTPClient(put)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
@ -300,21 +302,21 @@ type targetData struct {
// resolveTargetTemplate returns the resolved target template with replaced variables
// Those variables can be replaced by the given context, goos, goarch, goarm and more
func resolveTargetTemplate(ctx *context.Context, artifactory config.Put, artifact artifact.Artifact) (string, error) {
func resolveTargetTemplate(ctx *context.Context, put *config.Put, artifact artifact.Artifact) (string, error) {
data := targetData{
Version: ctx.Version,
Tag: ctx.Git.CurrentTag,
ProjectName: ctx.Config.ProjectName,
}
if artifactory.Mode == ModeBinary {
if put.Mode == ModeBinary {
data.Os = replace(ctx.Config.Archive.Replacements, artifact.Goos)
data.Arch = replace(ctx.Config.Archive.Replacements, artifact.Goarch)
data.Arm = replace(ctx.Config.Archive.Replacements, artifact.Goarm)
}
var out bytes.Buffer
t, err := template.New(ctx.Config.ProjectName).Parse(artifactory.Target)
t, err := template.New(ctx.Config.ProjectName).Parse(put.Target)
if err != nil {
return "", err
}

View File

@ -2,12 +2,14 @@ package http
import (
"bytes"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
h "net/http"
"net/http/httptest"
"os"
"strings"
"sync"
"testing"
@ -18,20 +20,6 @@ import (
"github.com/goreleaser/goreleaser/pkg/context"
)
var (
mux *h.ServeMux
srv *httptest.Server
)
func setup() {
mux = h.NewServeMux()
srv = httptest.NewServer(mux)
}
func teardown() {
srv.Close()
}
func TestAssetOpenDefault(t *testing.T) {
tf, err := ioutil.TempFile("", "")
if err != nil {
@ -112,6 +100,7 @@ func TestCheckConfig(t *testing.T) {
{"name missing", args{ctx, &config.Put{Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true},
{"mode missing", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe"}, "test"}, true},
{"mode invalid", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe", Mode: "blabla"}, "test"}, true},
{"cert invalid", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeBinary, TrustedCerts: "bad cert!"}, "test"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -196,11 +185,10 @@ func doCheck(c check, r *h.Request) error {
}
func TestUpload(t *testing.T) {
setup()
defer teardown()
content := []byte("blah!")
requests := []*h.Request{}
var m sync.Mutex
mux := h.NewServeMux()
mux.Handle("/", h.HandlerFunc(func(w h.ResponseWriter, r *h.Request) {
bs, err := ioutil.ReadAll(r.Body)
if err != nil {
@ -247,37 +235,93 @@ func TestUpload(t *testing.T) {
} {
ctx.Artifacts.Add(artifact.Artifact{Name: "a." + a.ext, Path: "/a/a." + a.ext, Type: a.typ})
}
tests := []struct {
name string
ctx *context.Context
wantErr bool
put config.Put
check func(r []*h.Request) error
name string
tryPlain bool
tryTLS bool
wantErrPlain bool
wantErrTLS bool
setup func(*httptest.Server) (*context.Context, config.Put)
check func(r []*h.Request) error
}{
{"wrong-mode", ctx, true,
config.Put{Mode: "wrong-mode", Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/", Username: "u1"},
{"wrong-mode", true, true, true, true,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: "wrong-mode",
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
Username: "u1",
TrustedCerts: cert(s),
}
},
checks(),
},
{"username-from-env", ctx, false,
config.Put{Mode: ModeArchive, Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/"},
{"username-from-env", true, true, false, false,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeArchive,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
TrustedCerts: cert(s),
}
},
checks(
check{"/blah/2.1.0/a.deb", "u2", "x", content},
check{"/blah/2.1.0/a.tar", "u2", "x", content},
),
},
{"archive", ctx, false,
config.Put{Mode: ModeArchive, Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/", Username: "u1"},
{"archive", true, true, false, false,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeArchive,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
Username: "u1",
TrustedCerts: cert(s),
}
},
checks(
check{"/blah/2.1.0/a.deb", "u1", "x", content},
check{"/blah/2.1.0/a.tar", "u1", "x", content},
),
},
{"binary", ctx, false,
config.Put{Mode: ModeBinary, Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/", Username: "u2"},
{"binary", true, true, false, false,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeBinary,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
Username: "u2",
TrustedCerts: cert(s),
}
},
checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content}),
},
{"archive-with-checksum-and-signature", ctx, false,
config.Put{Mode: ModeArchive, Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/", Username: "u3", Checksum: true, Signature: true},
{"binary-add-ending-bar", true, true, false, false,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeBinary,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}",
Username: "u2",
TrustedCerts: cert(s),
}
},
checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content}),
},
{"archive-with-checksum-and-signature", true, true, false, false,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeArchive,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
Username: "u3",
Checksum: true,
Signature: true,
TrustedCerts: cert(s),
}
},
checks(
check{"/blah/2.1.0/a.deb", "u3", "x", content},
check{"/blah/2.1.0/a.tar", "u3", "x", content},
@ -285,16 +329,100 @@ func TestUpload(t *testing.T) {
check{"/blah/2.1.0/a.sig", "u3", "x", content},
),
},
{"bad-template", true, true, true, true,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeBinary,
Name: "a",
Target: s.URL + "/{{.ProjectNameXXX}}/{{.VersionXXX}}/",
Username: "u3",
Checksum: true,
Signature: true,
TrustedCerts: cert(s),
}
},
checks(),
},
{"failed-request", true, true, true, true,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeBinary,
Name: "a",
Target: s.URL[0:strings.LastIndex(s.URL, ":")] + "/{{.ProjectName}}/{{.Version}}/",
Username: "u3",
Checksum: true,
Signature: true,
TrustedCerts: cert(s),
}
},
checks(),
},
{"broken-cert", false, true, false, true,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeBinary,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
Username: "u3",
Checksum: false,
Signature: false,
TrustedCerts: "bad certs!",
}
},
checks(),
},
{"skip-publishing", true, true, true, true,
func(s *httptest.Server) (*context.Context, config.Put) {
c := *ctx
c.SkipPublish = true
return &c, config.Put{}
},
checks(),
},
}
uploadAndCheck := func(setup func(*httptest.Server) (*context.Context, config.Put), wantErrPlain, wantErrTLS bool, check func(r []*h.Request) error, srv *httptest.Server) {
requests = nil
ctx, put := setup(srv)
wantErr := wantErrPlain
if srv.Certificate() != nil {
wantErr = wantErrTLS
}
if err := Upload(ctx, []config.Put{put}, "test", is2xx); (err != nil) != wantErr {
t.Errorf("Upload() error = %v, wantErr %v", err, wantErr)
}
if err := check(requests); err != nil {
t.Errorf("Upload() request invalid. Error: %v", err)
}
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
requests = nil
if err := Upload(tt.ctx, []config.Put{tt.put}, "test", is2xx); (err != nil) != tt.wantErr {
t.Errorf("Upload() error = %v, wantErr %v", err, tt.wantErr)
}
if err := tt.check(requests); err != nil {
t.Errorf("Upload() request invalid. Error: %v", err)
}
})
if tt.tryPlain {
t.Run(tt.name, func(t *testing.T) {
srv := httptest.NewServer(mux)
defer srv.Close()
uploadAndCheck(tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv)
})
}
if tt.tryTLS {
t.Run(tt.name+"-tls", func(t *testing.T) {
srv := httptest.NewUnstartedServer(mux)
srv.StartTLS()
defer srv.Close()
uploadAndCheck(tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv)
})
}
}
}
func cert(srv *httptest.Server) string {
if srv == nil || srv.Certificate() == nil {
return ""
}
block := &pem.Block{
Type: "CERTIFICATE",
Bytes: srv.Certificate().Raw,
}
return string(pem.EncodeToMemory(block))
}

View File

@ -287,12 +287,13 @@ type S3 struct {
// Put HTTP upload configuration
type Put struct {
Name string `yaml:",omitempty"`
Target string `yaml:",omitempty"`
Username string `yaml:",omitempty"`
Mode string `yaml:",omitempty"`
Checksum bool `yaml:",omitempty"`
Signature bool `yaml:",omitempty"`
Name string `yaml:",omitempty"`
Target string `yaml:",omitempty"`
Username string `yaml:",omitempty"`
Mode string `yaml:",omitempty"`
Checksum bool `yaml:",omitempty"`
Signature bool `yaml:",omitempty"`
TrustedCerts string `yaml:"trusted_certificates,omitempty"`
}
// Project includes all project configuration
@ -317,7 +318,6 @@ type Project struct {
EnvFiles EnvFiles `yaml:"env_files,omitempty"`
Git Git `yaml:",omitempty"`
Before Before `yaml:",omitempty"`
TrustedCerts string `yaml:"trusted_certificates,omitempty"`
// this is a hack ¯\_(ツ)_/¯
SingleBuild Build `yaml:"build,omitempty"`

View File

@ -95,21 +95,25 @@ certificate chain in your configuration.
The trusted certificate chain will be used to validate the server certificates.
You can set the trusted certificate chain using the global `trusted_certificates`
setting and PEM encoded certificates on a YAML literal block like this:
You can set the trusted certificate chain using the `trusted_certificates`
setting the artifactory section with PEM encoded certificates on a YAML literal
block like this:
```yaml
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
puts:
- name: "some artifactory server with a private TLS certificate"
#...(other settings)...
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
```
## Customization
@ -136,13 +140,13 @@ artifactories:
checksum: true
# Upload signatures (defaults to false)
signature: true
# Certificate chain used to validate server certificates
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
# Certificate chain used to validate server certificates
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
```
These settings should allow you to push your artifacts into multiple Artifactories.

View File

@ -5,7 +5,6 @@ hideFromIndex: true
weight: 120
---
Since [vX.Y.Z](https://github.com/goreleaser/goreleaser/releases/tag/vX.Y.Z),
GoReleaser supports building and pushing artifacts to HTTP servers using simple HTTP PUT requests.
## How it works
@ -88,25 +87,29 @@ The name will be transformed to uppercase.
### Server authentication
You can authenticate your TLS server adding a trusted X.509 certificate chain
in your configuration.
in your put configuration.
The trusted certificate chain will be used to validate the server certificates.
You can set the trusted certificate chain using the global `trusted_certificates`
setting and PEM encoded certificates on a YAML literal block like this:
You can set the trusted certificate chain using the `trusted_certificates`
setting the put section with PEM encoded certificates on a YAML literal block
like this:
```yaml
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
puts:
- name: "some HTTP/TLS server"
#...(other settings)...
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
```
## Customization
@ -133,13 +136,13 @@ puts:
checksum: true
# Upload signatures (defaults to false)
signature: true
# Certificate chain used to validate server certificates
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
# Certificate chain used to validate server certificates
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
```
These settings should allow you to push your artifacts into multiple HTTP servers.