diff --git a/Dockerfile b/Dockerfile index 365c16d3..4fa23db8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,12 @@ FROM golang:1.14.12 as golang +ARG VERSION=undefined WORKDIR /go/src/github.com/IBM/portieris RUN mkdir -p /go/src/github.com/IBM/portieris COPY . ./ -RUN CGO_ENABLED=0 GOOS=linux go build -a -tags containers_image_openpgp -o ./bin/portieris ./cmd/portieris +RUN CGO_ENABLED=0 GOOS=linux go build \ + -ldflags="-X github.com/IBM/portieris/internal/info.Version=$VERSION" -a \ + -tags containers_image_openpgp -o ./bin/portieris ./cmd/portieris FROM scratch COPY --from=golang /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ diff --git a/Makefile b/Makefile index 824189c2..8cdc5b01 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ GOTAGS='containers_image_openpgp' .PHONY: test image: - docker build -t portieris:$(TAG) . + docker build --build-arg VERSION=$(VERSION) -t portieris:$(TAG) . push: image docker tag portieris:$(TAG) $(HUB)/portieris:$(TAG) diff --git a/cmd/portieris/main.go b/cmd/portieris/main.go index 55b7b31a..3a9087de 100644 --- a/cmd/portieris/main.go +++ b/cmd/portieris/main.go @@ -1,4 +1,4 @@ -// Copyright 2018,2020 Portieris Authors. +// Copyright 2018,2021 Portieris Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,18 +22,18 @@ import ( "os" "strings" - "github.com/IBM/portieris/pkg/metrics" - notaryclient "github.com/IBM/portieris/pkg/notary" - "github.com/gorilla/mux" - "github.com/prometheus/client_golang/prometheus/promhttp" - kube "github.com/IBM/portieris/helpers/kube" + "github.com/IBM/portieris/internal/info" "github.com/IBM/portieris/pkg/controller/multi" "github.com/IBM/portieris/pkg/kubernetes" + "github.com/IBM/portieris/pkg/metrics" + notaryclient "github.com/IBM/portieris/pkg/notary" registryclient "github.com/IBM/portieris/pkg/registry" notaryverifier "github.com/IBM/portieris/pkg/verifier/trust" "github.com/IBM/portieris/pkg/webhook" "github.com/golang/glog" + "github.com/gorilla/mux" + "github.com/prometheus/client_golang/prometheus/promhttp" ) func main() { @@ -63,6 +63,8 @@ func main() { os.Exit(0) } + glog.Info("Starting portieris ", info.Version) + kubeClientConfig := kube.GetKubeClientConfig(kubeconfig) kubeClientset := kube.GetKubeClient(kubeClientConfig) kubeWrapper := kubernetes.NewKubeClientsetWrapper(kubeClientset) diff --git a/helpers/kube/kube.go b/helpers/kube/kube.go index d81e7622..8ad0cc30 100644 --- a/helpers/kube/kube.go +++ b/helpers/kube/kube.go @@ -18,6 +18,7 @@ import ( "fmt" "os" + "github.com/IBM/portieris/internal/info" portierisclientset "github.com/IBM/portieris/pkg/apis/portieris.cloud.ibm.com/client/clientset/versioned" "github.com/IBM/portieris/pkg/policy" "github.com/golang/glog" @@ -60,6 +61,8 @@ func GetKubeClientConfig(kubeconfigFileLoc *string) *rest.Config { glog.Fatal(err) } + config.UserAgent = "portieris/" + info.Version + return config } diff --git a/helpers/oauth/oauth.go b/helpers/oauth/oauth.go index bbfdce36..25eed5b2 100644 --- a/helpers/oauth/oauth.go +++ b/helpers/oauth/oauth.go @@ -1,4 +1,4 @@ -// Copyright 2018 Portieris Authors. +// Copyright 2018,2021 Portieris Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import ( "os" "time" + "github.com/IBM/portieris/helpers/useragent" "github.com/golang/glog" ) @@ -49,19 +50,21 @@ func GetHTTPClient(customFile string) *http.Client { client := &http.Client{ Timeout: 10 * time.Minute, - Transport: &http.Transport{ - Dial: (&net.Dialer{ - Timeout: 5 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - DisableKeepAlives: false, - MaxIdleConnsPerHost: 10, - TLSHandshakeTimeout: 5 * time.Second, - TLSClientConfig: &tls.Config{ - // Avoid fallback by default to SSL protocols < TLS1.2 - MinVersion: tls.VersionTLS12, - PreferServerCipherSuites: true, - RootCAs: rootCA, + Transport: &useragent.Set{ + Transport: &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 5 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + DisableKeepAlives: false, + MaxIdleConnsPerHost: 10, + TLSHandshakeTimeout: 5 * time.Second, + TLSClientConfig: &tls.Config{ + // Avoid fallback by default to SSL protocols < TLS1.2 + MinVersion: tls.VersionTLS12, + PreferServerCipherSuites: true, + RootCAs: rootCA, + }, }, }, } diff --git a/helpers/useragent/useragent.go b/helpers/useragent/useragent.go new file mode 100644 index 00000000..147f4b59 --- /dev/null +++ b/helpers/useragent/useragent.go @@ -0,0 +1,33 @@ +// Copyright 2021 Portieris Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package useragent + +import ( + "net/http" + + "github.com/IBM/portieris/internal/info" +) + +// Set is a http.RoundTripper which adds the User-Agent header to all requests. +type Set struct { + Transport http.RoundTripper +} + +// RoundTrip sets the User-Agent on the request and then calls the underlying +// Transport. +func (a *Set) RoundTrip(r *http.Request) (*http.Response, error) { + r.Header.Set("User-Agent", "portieris/"+info.Version) + return a.Transport.RoundTrip(r) +} diff --git a/helpers/useragent/useragent_test.go b/helpers/useragent/useragent_test.go new file mode 100644 index 00000000..e51f239a --- /dev/null +++ b/helpers/useragent/useragent_test.go @@ -0,0 +1,70 @@ +// Copyright 2021 Portieris Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package useragent + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSet_RoundTrip(t *testing.T) { + tests := map[string]struct { + wantStatus int + wantErr bool + }{ + "good path": { + wantStatus: http.StatusTeapot, + }, + "error path": { + wantErr: true, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + c := &http.Client{ + Transport: &Set{ + Transport: http.DefaultTransport, + }, + } + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("User-Agent"), "portieris/undefined") + w.WriteHeader(test.wantStatus) + })) + defer ts.Close() + + r, err := http.NewRequest(http.MethodGet, ts.URL, nil) + require.NoError(t, err) + if test.wantErr { + r, err = http.NewRequest(http.MethodGet, "htootyps://notaurl", nil) + require.NoError(t, err) + } + + res, err := c.Do(r) + + if (err != nil) != test.wantErr { + t.Errorf("error = %v, wantErr %v", err, test.wantErr) + return + } + if !test.wantErr { + assert.Equal(t, res.StatusCode, test.wantStatus) + } + }) + } +} diff --git a/internal/info/info.go b/internal/info/info.go new file mode 100644 index 00000000..a0163112 --- /dev/null +++ b/internal/info/info.go @@ -0,0 +1,19 @@ +// Copyright 2021 Portieris Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package info + +// Version is replaced with the correct value when portieris is built using the +// Makefile. +var Version = "undefined" diff --git a/pkg/notary/notary.go b/pkg/notary/notary.go index 0be1f183..468b8401 100644 --- a/pkg/notary/notary.go +++ b/pkg/notary/notary.go @@ -1,4 +1,4 @@ -// Copyright 2018 Portieris Authors. +// Copyright 2018,2021 Portieris Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,11 +23,11 @@ import ( "os" "time" + "github.com/IBM/portieris/internal/info" "github.com/docker/distribution/registry/client/transport" + notaryclient "github.com/theupdateframework/notary/client" "github.com/theupdateframework/notary/trustpinning" "github.com/theupdateframework/notary/tuf/data" - - notaryclient "github.com/theupdateframework/notary/client" ) // Client . @@ -90,14 +90,14 @@ func (c Client) makeHubTransport(notaryToken string) http.RoundTripper { modifiers := []transport.RequestModifier{ transport.NewHeaderRequestModifier(http.Header{ - "User-Agent": []string{"portieris-client"}, + "User-Agent": []string{"portieris/" + info.Version}, }), } if notaryToken != "" { modifiers = []transport.RequestModifier{ transport.NewHeaderRequestModifier(http.Header{ - "User-Agent": []string{"portieris-client"}, + "User-Agent": []string{"portieris/" + info.Version}, "Authorization": []string{fmt.Sprintf("Bearer %s", notaryToken)}, }), } diff --git a/pkg/verifier/simple/imagePolicy.go b/pkg/verifier/simple/imagePolicy.go index 96985b47..e4df3357 100644 --- a/pkg/verifier/simple/imagePolicy.go +++ b/pkg/verifier/simple/imagePolicy.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/IBM/portieris/helpers/credential" + "github.com/IBM/portieris/internal/info" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/image" "github.com/containers/image/v5/manifest" @@ -44,8 +45,8 @@ func (v verifier) VerifyByPolicy(imageToVerify string, credentials credential.Cr } // if expensive, make instance systemContext := &types.SystemContext{ - RootForImplicitAbsolutePaths: "/nowhere", // read nothing from files - DockerRegistryUserAgent: "portieris", // add version? + RootForImplicitAbsolutePaths: "/nowhere", // read nothing from files + DockerRegistryUserAgent: "portieris/" + info.Version, RegistriesDirPath: registriesConfigDir, }