Skip to content

Commit

Permalink
Add credential consumption for protected buildpacks
Browse files Browse the repository at this point in the history
Co-authored-by: Nicolas Bender <[email protected]>
  • Loading branch information
pbusko and nicolasbender committed May 17, 2024
1 parent c68023b commit 0adc621
Show file tree
Hide file tree
Showing 8 changed files with 403 additions and 22 deletions.
15 changes: 14 additions & 1 deletion cmd/builder/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"code.cloudfoundry.org/cnbapplifecycle/pkg/archive"
"code.cloudfoundry.org/cnbapplifecycle/pkg/errors"
"code.cloudfoundry.org/cnbapplifecycle/pkg/keychain"
"code.cloudfoundry.org/cnbapplifecycle/pkg/log"
"code.cloudfoundry.org/cnbapplifecycle/pkg/staging"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -100,7 +101,19 @@ var builderCmd = &cobra.Command{
return errors.ErrGenericBuild
}

err = staging.DownloadBuildpacks(buildpacks, buildpacksDir, image.NewFetcher(logger, nil), blob.NewDownloader(logger, downloadCacheDir), orderFile, logger)
creds, err := keychain.FromEnv()
if err != nil {
logger.Errorf("failed to parse %s environment variable, error: %s\n", keychain.CnbCredentialsEnv, err.Error())
return errors.ErrGenericBuild
}
err = staging.DownloadBuildpacks(
buildpacks,
buildpacksDir,
image.NewFetcher(logger, nil, image.WithKeychain(creds)),
blob.NewDownloader(logger, downloadCacheDir, blob.WithClient(keychain.NewHTTPClient(creds))),
orderFile,
logger,
)
if err != nil {
logger.Errorf("failed to download buildpacks, error: %s\n", err.Error())
return errors.ErrDownloadingBuildpack
Expand Down
16 changes: 7 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ module code.cloudfoundry.org/cnbapplifecycle

go 1.22

toolchain go1.22.2

require (
github.com/BurntSushi/toml v1.3.2
github.com/apex/log v1.9.0
github.com/buildpacks/lifecycle v0.19.4
github.com/buildpacks/pack v0.33.3-0.20240508211754-0a39de09a078
github.com/docker/docker v26.1.1+incompatible
github.com/onsi/ginkgo/v2 v2.17.2
github.com/buildpacks/lifecycle v0.19.6
github.com/buildpacks/pack v0.33.3-0.20240516162812-884dd1837311
github.com/docker/docker v26.1.3+incompatible
github.com/google/go-containerregistry v0.19.1
github.com/jarcoal/httpmock v1.3.1
github.com/onsi/ginkgo/v2 v2.17.3
github.com/onsi/gomega v1.33.1
github.com/spf13/cobra v1.8.0
github.com/testcontainers/testcontainers-go v0.30.0
github.com/testcontainers/testcontainers-go v0.31.0
)

require (
Expand Down Expand Up @@ -75,7 +75,6 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-containerregistry v0.19.1 // indirect
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
Expand Down Expand Up @@ -119,7 +118,6 @@ require (
go.opentelemetry.io/otel/metric v1.25.0 // indirect
go.opentelemetry.io/otel/trace v1.25.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20231219160207-73b9e39aefca // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
Expand Down
26 changes: 14 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/buildpacks/imgutil v0.0.0-20240507132533-9f7b96c3d09d h1:GVRuY/C8j4pjOddeeZelbKKLMMX+dYR3TlxE4L1hECU=
github.com/buildpacks/imgutil v0.0.0-20240507132533-9f7b96c3d09d/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk=
github.com/buildpacks/lifecycle v0.19.4 h1:lATTWyMs4m4+3+n6PgvmB0lBgut2+b8eZ+iu/wXwhrI=
github.com/buildpacks/lifecycle v0.19.4/go.mod h1:sWrBJzf/7dWrcHrWiV/P2+3jS8G8Ki5tczq8jO3XVRQ=
github.com/buildpacks/pack v0.33.3-0.20240508211754-0a39de09a078 h1:PGEBo3DVEEsFtimT6uTZDgnBAXZIpSejUcSqAw1BuTA=
github.com/buildpacks/pack v0.33.3-0.20240508211754-0a39de09a078/go.mod h1:V84RLsUBm+0GKazl31GCVpCOm4LFu+oVNbtUPRhXeFc=
github.com/buildpacks/lifecycle v0.19.6 h1:/bmfMs35aSkxyzYDF+iHl9VnYmUBBbHBmnvo8XNEINk=
github.com/buildpacks/lifecycle v0.19.6/go.mod h1:sWrBJzf/7dWrcHrWiV/P2+3jS8G8Ki5tczq8jO3XVRQ=
github.com/buildpacks/pack v0.33.3-0.20240516162812-884dd1837311 h1:8aNPCj8EIPzVKfiuOhLdcYmRCVx+ORXRVVBBx9w+W10=
github.com/buildpacks/pack v0.33.3-0.20240516162812-884dd1837311/go.mod h1:dOPu6HKn29mtZULrNPMZ7gqyPsu6jMVNhmp5vsH0up4=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
Expand Down Expand Up @@ -128,8 +128,8 @@ github.com/docker/cli v26.1.1+incompatible h1:bE1/uE2tCa08fMv+7ikLR/RDPoCqytwrLt
github.com/docker/cli v26.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v26.1.1+incompatible h1:oI+4kkAgIwwb54b9OC7Xc3hSgu1RlJA/Lln/DF72djQ=
github.com/docker/docker v26.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo=
github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
Expand Down Expand Up @@ -213,6 +213,8 @@ github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
Expand Down Expand Up @@ -257,6 +259,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
Expand Down Expand Up @@ -284,8 +288,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU=
github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
Expand Down Expand Up @@ -373,8 +377,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/testcontainers/testcontainers-go v0.30.0 h1:jmn/XS22q4YRrcMwWg0pAwlClzs/abopbsBzrepyc4E=
github.com/testcontainers/testcontainers-go v0.30.0/go.mod h1:K+kHNGiM5zjklKjgTtcrEetF3uhWbMUyqAQoyoh8Pf0=
github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U=
github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
Expand Down Expand Up @@ -428,8 +432,6 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20231219160207-73b9e39aefca h1:+xQfFu/HO/82Wwg4zuJ5xiLp0yaOLJjBGnuafXp85YQ=
golang.org/x/exp v0.0.0-20231219160207-73b9e39aefca/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
Expand Down
63 changes: 63 additions & 0 deletions pkg/keychain/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package keychain

import (
"fmt"
"net/http"
"net/url"

"github.com/google/go-containerregistry/pkg/authn"
)

func NewHTTPClient(keychain authn.Keychain) *http.Client {
return &http.Client{
Transport: &roundTripper{
keychain: keychain,
inner: http.DefaultTransport,
},
}
}

type roundTripper struct {
keychain authn.Keychain
inner http.RoundTripper
}

func (rt *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
if rt.keychain == nil {
return rt.inner.RoundTrip(req)
}

authenticator, err := rt.keychain.Resolve(&urlResource{url: req.URL})
if err != nil {
return nil, err
}

if authenticator == authn.Anonymous {
return rt.inner.RoundTrip(req)
}

conf, err := authenticator.Authorization()
if err != nil {
return nil, err
}

if conf.RegistryToken != "" {
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", conf.RegistryToken))
} else {
req.SetBasicAuth(conf.Username, conf.Password)
}

return rt.inner.RoundTrip(req)
}

type urlResource struct {
url *url.URL
}

func (r *urlResource) RegistryStr() string {
return r.url.Hostname()
}

func (r *urlResource) String() string {
return r.RegistryStr()
}
113 changes: 113 additions & 0 deletions pkg/keychain/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package keychain_test

import (
"bytes"
"fmt"
"io"
"net/http"
"os"

"code.cloudfoundry.org/cnbapplifecycle/pkg/keychain"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/jarcoal/httpmock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("HTTP RoundTripper", func() {
var (
creds authn.Keychain
client *http.Client
err error
)

BeforeEach(func() {
creds, err = keychain.FromEnv()
Expect(err).ToNot(HaveOccurred())
client = keychain.NewHTTPClient(creds)
})

Describe("RoundTrip", func() {
It("works when keychain is nil", func() {
httpmock.RegisterResponder("GET", "https://test.io", func(r *http.Request) (*http.Response, error) {
if a := r.Header.Get("Authorization"); a != "" {
return httpmock.NewStringResponse(http.StatusBadRequest, fmt.Sprintf("found authorization header: %q", a)), nil
}
return httpmock.NewStringResponse(http.StatusOK, ""), nil
})

client = keychain.NewHTTPClient(nil)

res, err := client.Get("https://test.io")
Expect(err).ToNot(HaveOccurred())
defer res.Body.Close()
Expect(res.StatusCode).To(Equal(http.StatusOK))
})

It("works with the default keychain (anonymous)", func() {
httpmock.RegisterResponder("GET", "https://test.io", func(r *http.Request) (*http.Response, error) {
if a := r.Header.Get("Authorization"); a != "" {
return httpmock.NewStringResponse(http.StatusBadRequest, fmt.Sprintf("found authorization header: %q", a)), nil
}
return httpmock.NewStringResponse(http.StatusOK, ""), nil
})
res, err := client.Get("https://test.io")
Expect(err).ToNot(HaveOccurred())
defer res.Body.Close()
Expect(res.StatusCode).To(Equal(http.StatusOK))
})

Describe("with credentials", func() {
BeforeEach(func() {
Expect(os.Setenv(keychain.CnbCredentialsEnv, `{"bearer.test":{"token":"foo"},"basic.test":{"username":"foo","password":"bar"}}`)).To(Succeed())
creds, err = keychain.FromEnv()
Expect(err).ToNot(HaveOccurred())
client = keychain.NewHTTPClient(creds)
})

AfterEach(func() {
Expect(os.Unsetenv(keychain.CnbCredentialsEnv)).To(Succeed())
})

It("sets Authorization header (token)", func() {
httpmock.RegisterResponder("GET", "https://bearer.test", func(r *http.Request) (*http.Response, error) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return httpmock.NewStringResponse(http.StatusBadRequest, "authorization header not found"), nil
}

return httpmock.NewStringResponse(http.StatusOK, authHeader), nil
})

res, err := client.Get("https://bearer.test")
Expect(err).ToNot(HaveOccurred())
defer res.Body.Close()
Expect(res.StatusCode).To(Equal(http.StatusOK))

body := bytes.NewBuffer(nil)
io.Copy(body, res.Body)
Expect(body.String()).To(Equal("Bearer foo"))
})

It("sets Authorization header (basic)", func() {
httpmock.RegisterResponder("GET", "https://basic.test", func(r *http.Request) (*http.Response, error) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return httpmock.NewStringResponse(http.StatusBadRequest, "authorization header not found"), nil
}

return httpmock.NewStringResponse(http.StatusOK, authHeader), nil
})

res, err := client.Get("https://basic.test")
Expect(err).ToNot(HaveOccurred())
defer res.Body.Close()
Expect(res.StatusCode).To(Equal(http.StatusOK))

body := bytes.NewBuffer(nil)
io.Copy(body, res.Body)
Expect(body.String()).To(Equal("Basic Zm9vOmJhcg=="))
})
})
})
})
66 changes: 66 additions & 0 deletions pkg/keychain/keychain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package keychain

import (
"encoding/json"
"errors"
"os"

"github.com/google/go-containerregistry/pkg/authn"
)

const CnbCredentialsEnv = "CNB_REGISTRY_CREDS"

type auth struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
}

func (a auth) config() (authn.AuthConfig, error) {
if !((a.Username != "" && a.Password != "" && a.Token == "") || (a.Token != "" && a.Username == "" && a.Password == "")) {
return authn.AuthConfig{}, errors.New("invalid credential combination")
}

if a.Token != "" {
return authn.AuthConfig{
RegistryToken: a.Token,
}, nil
}

return authn.AuthConfig{
Username: a.Username,
Password: a.Password,
}, nil
}

type envKeyChain struct {
credentials map[string]auth
}

func FromEnv() (authn.Keychain, error) {
value, ok := os.LookupEnv(CnbCredentialsEnv)
if !ok {
return authn.DefaultKeychain, nil
}

e := &envKeyChain{}
if err := json.Unmarshal([]byte(value), &e.credentials); err != nil {
return nil, err
}

return authn.NewMultiKeychain(e, authn.DefaultKeychain), nil
}

func (e *envKeyChain) Resolve(resource authn.Resource) (authn.Authenticator, error) {
creds, ok := e.credentials[resource.RegistryStr()]
if !ok {
return authn.Anonymous, nil
}

config, err := creds.config()
if err != nil {
return nil, err
}

return authn.FromConfig(config), nil
}
Loading

0 comments on commit 0adc621

Please sign in to comment.