From 3837cc53a64e149af0e70d878662c1c21293323a Mon Sep 17 00:00:00 2001 From: Niklas Date: Fri, 24 Jan 2025 12:44:34 +0100 Subject: [PATCH 1/5] #85 Add proxy configuration --- CHANGELOG.md | 2 + docs/development/proxy_configuration_en.md | 35 ++++++ k8s/helm/templates/deployment.yaml | 6 + pkg/helm/client/client.go | 79 ++++++++++++- pkg/helm/client/client_test.go | 127 +++++++++++++++++++++ 5 files changed, 243 insertions(+), 6 deletions(-) create mode 100644 docs/development/proxy_configuration_en.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 099a85d..a4830a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- [#85] Proxy support for the registry http client in helm. The proxy will be used from the secret `ces-proxy` which will be created by the setup or the blueprint controller. ## [v1.6.2] - 2024-12-19 ### Fixed diff --git a/docs/development/proxy_configuration_en.md b/docs/development/proxy_configuration_en.md new file mode 100644 index 0000000..9998cc5 --- /dev/null +++ b/docs/development/proxy_configuration_en.md @@ -0,0 +1,35 @@ +# Developing with a proxy for external queries to dogu container or helm registry + +Component can mount a proxy stored in the `ces-proxy` secret. +To test this behaviour be sure to configure the proxy in the setup values of the setup's helm chart, +or create the secret manually by: + +`kubectl create secret generic ces-proxy --from-literal=url=http://test:test@192.168.56.1:3128 -n ecosystem` + +## Setup local proxy in docker + +### Run container with host network mode to reach the development registry in the cluster if required. + +- `docker run --net=host -d --name squid -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta` + +### Configure auth and connection to dev registry + +- `docker exec -it squid /bin/bash` +- `apt update && apt-get install -y apache2-utils` +- `htpasswd -c -d /etc/squid/passwords test` +- `chmod o+r /etc/squid/passwords` +- `apt-get install vim` +- `vi /etc/squid/conf.d/auth.conf` + - ``` + acl allcomputers src all + auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwords + auth_param basic realm proxy + acl authenticated proxy_auth REQUIRED + http_access allow authenticated allcomputers + ``` +- `echo "localhost k3ces.local" >> /etc/hosts` +- `squid -k reconfigure` + +### Check access logs + +- `docker logs -f squid` diff --git a/k8s/helm/templates/deployment.yaml b/k8s/helm/templates/deployment.yaml index 345fe97..22bf6df 100644 --- a/k8s/helm/templates/deployment.yaml +++ b/k8s/helm/templates/deployment.yaml @@ -40,6 +40,12 @@ spec: value: "{{ .Values.manager.env.helmClientTimeoutMins | default "15" }}" - name: ROLLBACK_RELEASE_TIMEOUT_MINS value: "{{ .Values.manager.env.rollbackReleaseTimeoutMins | default "15" }}" + - name: PROXY_URL + valueFrom: + secretKeyRef: + name: ces-proxy + key: url + optional: true image: "{{ .Values.manager.image.registry }}/{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}" imagePullPolicy: {{ .Values.manager.imagePullPolicy | default "IfNotPresent"}} livenessProbe: diff --git a/pkg/helm/client/client.go b/pkg/helm/client/client.go index 3048edb..ad54ade 100644 --- a/pkg/helm/client/client.go +++ b/pkg/helm/client/client.go @@ -5,11 +5,13 @@ import ( "crypto/tls" "fmt" "github.com/spf13/pflag" + "k8s.io/cli-runtime/pkg/genericclioptions" "log" + "net" "net/http" + "net/url" "os" - - "k8s.io/cli-runtime/pkg/genericclioptions" + "time" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" @@ -95,16 +97,81 @@ func createRegistryClient(options *Options, settings *cli.EnvSettings) (*registr registry.ClientOptCredentialsFile(settings.RegistryConfig), } + var err error + clientOpts, err = configureHttpRegistryClientOptions(options, clientOpts) + if err != nil { + return nil, err + } + + return registry.NewClient(clientOpts...) +} + +func configureHttpRegistryClientOptions(options *Options, clientOpts []registry.ClientOption) ([]registry.ClientOption, error) { if options.PlainHttp { clientOpts = append(clientOpts, registry.ClientOptPlainHTTP()) } + var httpTransport *http.Transport + var err error + httpTransport, err = getProxyTransportIfConfigured() + if err != nil { + return nil, err + } + + httpTransport = configureTls(options, httpTransport) + + if httpTransport != nil { + clientOpts = append(clientOpts, registry.ClientOptHTTPClient(&http.Client{Timeout: time.Second * 10, Transport: httpTransport})) + } + + return clientOpts, nil +} + +func configureTls(options *Options, transport *http.Transport) *http.Transport { if !options.PlainHttp && options.InsecureTls { - insecureHttpsClient := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} - httpClientOpt := registry.ClientOptHTTPClient(insecureHttpsClient) - clientOpts = append(clientOpts, httpClientOpt) + tlsConfig := &tls.Config{InsecureSkipVerify: true} + if transport != nil { + transport.TLSClientConfig = tlsConfig + } else { + transport = &http.Transport{TLSClientConfig: tlsConfig} + } } - return registry.NewClient(clientOpts...) + + return transport +} + +func getProxyTransportIfConfigured() (*http.Transport, error) { + proxyURL, found := os.LookupEnv("PROXY_URL") + if !found || len(proxyURL) < 1 { + return nil, nil + } + + parsedProxy, err := url.Parse(proxyURL) + if err != nil { + return nil, fmt.Errorf("failed to parse proxy: %w", err) + } + + proxyFn := func(request *http.Request) (*url.URL, error) { + return parsedProxy, nil + } + + return &http.Transport{ + // From https://github.com/google/go-containerregistry/blob/31786c6cbb82d6ec4fb8eb79cd9387905130534e/pkg/v1/remote/options.go#L87 + DisableCompression: true, + DialContext: (&net.Dialer{ + // By default we wrap the transport in retries, so reduce the + // default dial timeout to 5s to avoid 5x 30s of connection + // timeouts when doing the "ping" on certain http registries. + Timeout: 5 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + Proxy: proxyFn, + }, nil } // setEnvSettings sets the client's environment settings based on the provided client configuration. diff --git a/pkg/helm/client/client_test.go b/pkg/helm/client/client_test.go index 9c99372..2b87b47 100644 --- a/pkg/helm/client/client_test.go +++ b/pkg/helm/client/client_test.go @@ -2,10 +2,14 @@ package client import ( "context" + "fmt" "github.com/cloudogu/k8s-component-operator/pkg/helm/client/values" "github.com/stretchr/testify/mock" "helm.sh/helm/v3/pkg/chart" "log" + "net" + "net/http" + "net/url" "os" "strings" "testing" @@ -1163,3 +1167,126 @@ func TestHelmClient_GetChart(t *testing.T) { assert.Equal(t, "testdata/deprecated-chart", chartPath) }) } + +func Test_getProxyTransportIfConfigured(t *testing.T) { + testProxy := "http://user:pass@host:3128" + + parsedProxy, err := url.Parse(testProxy) + require.NoError(t, err) + + testProxyFn := func(request *http.Request) (*url.URL, error) { + return parsedProxy, nil + } + + expectedTransport := &http.Transport{ + // From https://github.com/google/go-containerregistry/blob/31786c6cbb82d6ec4fb8eb79cd9387905130534e/pkg/v1/remote/options.go#L87 + DisableCompression: true, + DialContext: (&net.Dialer{ + // By default we wrap the transport in retries, so reduce the + // default dial timeout to 5s to avoid 5x 30s of connection + // timeouts when doing the "ping" on certain http registries. + Timeout: 5 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + Proxy: testProxyFn, + } + + tests := []struct { + name string + wantErr assert.ErrorAssertionFunc + setEnv func(t *testing.T) + expectFn func(t *testing.T, got *http.Transport) + }{ + { + name: "return with proxy if configured", + expectFn: func(t *testing.T, got *http.Transport) { + assert.Equalf(t, expectedTransport.DisableCompression, got.DisableCompression, "getProxyTransportIfConfigured()") + assert.Equalf(t, expectedTransport.ForceAttemptHTTP2, got.ForceAttemptHTTP2, "getProxyTransportIfConfigured()") + assert.Equalf(t, expectedTransport.MaxIdleConns, got.MaxIdleConns, "getProxyTransportIfConfigured()") + assert.Equalf(t, expectedTransport.IdleConnTimeout, got.IdleConnTimeout, "getProxyTransportIfConfigured()") + assert.Equalf(t, expectedTransport.TLSHandshakeTimeout, got.TLSHandshakeTimeout, "getProxyTransportIfConfigured()") + assert.Equalf(t, expectedTransport.ExpectContinueTimeout, got.ExpectContinueTimeout, "getProxyTransportIfConfigured()") + + gotProxy, err := got.Proxy(nil) + require.NoError(t, err) + + wantProxy, err := expectedTransport.Proxy(nil) + require.NoError(t, err) + + assert.Equal(t, wantProxy, gotProxy) + }, + wantErr: assert.NoError, + setEnv: func(t *testing.T) { + t.Setenv("PROXY_URL", testProxy) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setEnv != nil { + tt.setEnv(t) + } + + got, err := getProxyTransportIfConfigured() + if !tt.wantErr(t, err, fmt.Sprintf("getProxyTransportIfConfigured()")) { + return + } + + tt.expectFn(t, got) + }) + } +} + +func Test_configureTls(t *testing.T) { + type args struct { + options *Options + transport *http.Transport + expectFn func(t *testing.T, transport *http.Transport) + } + tests := []struct { + name string + args args + expectFn func(t *testing.T, transport *http.Transport) + }{ + { + name: "should set tls config in existing transport", + args: args{ + options: &Options{ + PlainHttp: false, + InsecureTls: true, + }, + transport: http.DefaultTransport.(*http.Transport), + }, + expectFn: func(t *testing.T, transport *http.Transport) { + require.NotNil(t, transport.TLSClientConfig) + assert.Equal(t, true, transport.TLSClientConfig.InsecureSkipVerify) + }, + }, + { + name: "should create tls config in non existing transport", + args: args{ + options: &Options{ + PlainHttp: false, + InsecureTls: true, + }, + transport: nil, + }, + expectFn: func(t *testing.T, transport *http.Transport) { + require.NotNil(t, transport.TLSClientConfig) + assert.Equal(t, true, transport.TLSClientConfig.InsecureSkipVerify) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := configureTls(tt.args.options, tt.args.transport) + + tt.expectFn(t, got) + }) + } +} From 1607c6e34885114c05e3e81570cefa0dc0d9bf8a Mon Sep 17 00:00:00 2001 From: Niklas Date: Fri, 24 Jan 2025 13:41:31 +0100 Subject: [PATCH 2/5] #85 Update comments --- pkg/helm/client/client.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/helm/client/client.go b/pkg/helm/client/client.go index ad54ade..7f96267 100644 --- a/pkg/helm/client/client.go +++ b/pkg/helm/client/client.go @@ -156,12 +156,9 @@ func getProxyTransportIfConfigured() (*http.Transport, error) { } return &http.Transport{ - // From https://github.com/google/go-containerregistry/blob/31786c6cbb82d6ec4fb8eb79cd9387905130534e/pkg/v1/remote/options.go#L87 + // From https://github.com/google/go-containerregistry/blob/c4dd792fa06c1f8b780ad90c8ab4f38b4eac05bd/pkg/v1/remote/options.go#L113 DisableCompression: true, DialContext: (&net.Dialer{ - // By default we wrap the transport in retries, so reduce the - // default dial timeout to 5s to avoid 5x 30s of connection - // timeouts when doing the "ping" on certain http registries. Timeout: 5 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, From 964714a7b627be59770fb7d368e670067fa8cbc2 Mon Sep 17 00:00:00 2001 From: Niklas Date: Mon, 27 Jan 2025 10:17:05 +0100 Subject: [PATCH 3/5] #85 Edit confusing changelog entry. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4830a8..0c01aa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- [#85] Proxy support for the registry http client in helm. The proxy will be used from the secret `ces-proxy` which will be created by the setup or the blueprint controller. +- [#85] Proxy support for the registry http client in helm. The proxy will be used from the secret `ces-proxy` which will be created by the setup. ## [v1.6.2] - 2024-12-19 ### Fixed From 3b240bf590194d25b88e7bb47bb77e91ee04c491 Mon Sep 17 00:00:00 2001 From: Niklas Roeske Date: Mon, 27 Jan 2025 12:01:56 +0100 Subject: [PATCH 4/5] Bump version --- Dockerfile | 2 +- Makefile | 2 +- k8s/helm/component-patch-tpl.yaml | 2 +- k8s/helm/values.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index c183d45..108735e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,7 @@ RUN make compile-generic FROM gcr.io/distroless/static:nonroot LABEL maintainer="hello@cloudogu.com" \ NAME="k8s-component-operator" \ - VERSION="1.6.2" + VERSION="1.7.0" WORKDIR / COPY --from=builder /workspace/target/k8s-component-operator . diff --git a/Makefile b/Makefile index d27bc10..7557185 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Set these to the desired values ARTIFACT_ID=k8s-component-operator -VERSION=1.6.2 +VERSION=1.7.0 ## Image URL to use all building/pushing image targets IMAGE=cloudogu/${ARTIFACT_ID}:${VERSION} GOTAG?=1.23.4 diff --git a/k8s/helm/component-patch-tpl.yaml b/k8s/helm/component-patch-tpl.yaml index 47f187f..54a7cff 100644 --- a/k8s/helm/component-patch-tpl.yaml +++ b/k8s/helm/component-patch-tpl.yaml @@ -1,7 +1,7 @@ apiVersion: v1 values: images: - componentOperator: cloudogu/k8s-component-operator:1.6.2 + componentOperator: cloudogu/k8s-component-operator:1.7.0 patches: values.yaml: additionalImages: diff --git a/k8s/helm/values.yaml b/k8s/helm/values.yaml index 3578e90..4a6deaf 100644 --- a/k8s/helm/values.yaml +++ b/k8s/helm/values.yaml @@ -5,7 +5,7 @@ manager: image: registry: docker.io repository: cloudogu/k8s-component-operator - tag: 1.6.2 + tag: 1.7.0 imagePullPolicy: IfNotPresent env: logLevel: info From 0623fbddd8dda81a6d7e4b3cf0d17bfe2dce4494 Mon Sep 17 00:00:00 2001 From: Niklas Roeske Date: Mon, 27 Jan 2025 12:02:08 +0100 Subject: [PATCH 5/5] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c01aa5..12f4120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [v1.7.0] - 2025-01-27 ### Added - [#85] Proxy support for the registry http client in helm. The proxy will be used from the secret `ces-proxy` which will be created by the setup.