diff --git a/api/v1beta2/imagerepository_types.go b/api/v1beta2/imagerepository_types.go index 15311bef..ace74337 100644 --- a/api/v1beta2/imagerepository_types.go +++ b/api/v1beta2/imagerepository_types.go @@ -57,6 +57,11 @@ type ImageRepositorySpec struct { // +optional SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` + // ProxySecretRef specifies the Secret containing the proxy configuration + // to use while communicating with the container registry. + // +optional + ProxySecretRef *meta.LocalObjectReference `json:"proxySecretRef,omitempty"` + // ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate // the image pull if the service account has attached pull secrets. // +kubebuilder:validation:MaxLength=253 diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index c2928959..b486749c 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -248,6 +248,11 @@ func (in *ImageRepositorySpec) DeepCopyInto(out *ImageRepositorySpec) { *out = new(meta.LocalObjectReference) **out = **in } + if in.ProxySecretRef != nil { + in, out := &in.ProxySecretRef, &out.ProxySecretRef + *out = new(meta.LocalObjectReference) + **out = **in + } if in.CertSecretRef != nil { in, out := &in.CertSecretRef, &out.CertSecretRef *out = new(meta.LocalObjectReference) diff --git a/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml b/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml index dfa37b2c..e89a7403 100644 --- a/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml +++ b/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml @@ -373,6 +373,17 @@ spec: - azure - gcp type: string + proxySecretRef: + description: |- + ProxySecretRef specifies the Secret containing the proxy configuration + to use while communicating with the container registry. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object secretRef: description: |- SecretRef can be given the name of a secret containing diff --git a/docs/api/v1beta2/image-reflector.md b/docs/api/v1beta2/image-reflector.md index eeac1e11..393523e2 100644 --- a/docs/api/v1beta2/image-reflector.md +++ b/docs/api/v1beta2/image-reflector.md @@ -451,6 +451,21 @@ equivalent.

+proxySecretRef
+ + +github.com/fluxcd/pkg/apis/meta.LocalObjectReference + + + + +(Optional) +

ProxySecretRef specifies the Secret containing the proxy configuration +to use while communicating with the container registry.

+ + + + serviceAccountName
string @@ -651,6 +666,21 @@ equivalent.

+proxySecretRef
+ + +github.com/fluxcd/pkg/apis/meta.LocalObjectReference + + + + +(Optional) +

ProxySecretRef specifies the Secret containing the proxy configuration +to use while communicating with the container registry.

+ + + + serviceAccountName
string diff --git a/docs/spec/v1beta2/imagerepositories.md b/docs/spec/v1beta2/imagerepositories.md index 8ccb7f5d..372e79aa 100644 --- a/docs/spec/v1beta2/imagerepositories.md +++ b/docs/spec/v1beta2/imagerepositories.md @@ -229,6 +229,49 @@ data: deprecated. If you have any Secrets using these keys and specified in an ImageRepository, the controller will log a deprecation warning. +### Proxy secret reference + +`.spec.proxySecretRef.name` is an optional field used to specify the name of a +Secret that contains the proxy settings for the object. These settings are used +for all the remote operations related to the ImageRepository. +The Secret may contain three keys: + +- `address`, to specify the address of the proxy server. This is a required key. +- `username`, to specify the username to use if the proxy server is protected by + basic authentication. This is an optional key. +- `password`, to specify the password to use if the proxy server is protected by + basic authentication. This is an optional key. + +Example: + +```yaml +apiVersion: image.toolkit.fluxcd.io/v1beta2 +kind: ImageRepository +metadata: + name: example + namespace: default +spec: + interval: 5m0s + url: example.com + proxySecretRef: + name: http-proxy +--- +apiVersion: v1 +kind: Secret +metadata: + name: http-proxy +type: Opaque +stringData: + address: http://proxy.com + username: mandalorian + password: grogu +``` + +Proxying can also be configured in the image-reflector-controller Deployment directly by +using the standard environment variables such as `HTTPS_PROXY`, `ALL_PROXY`, etc. + +`.spec.proxySecretRef.name` takes precedence over all environment variables. + ### Suspend `.spec.suspend` is an optional field to suspend the reconciliation of an diff --git a/go.mod b/go.mod index 0bd62ca9..4595938d 100644 --- a/go.mod +++ b/go.mod @@ -8,11 +8,12 @@ require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 github.com/Masterminds/semver/v3 v3.2.1 github.com/dgraph-io/badger/v3 v3.2103.5 + github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 github.com/fluxcd/image-reflector-controller/api v0.32.0 github.com/fluxcd/pkg/apis/acl v0.3.0 github.com/fluxcd/pkg/apis/event v0.10.0 github.com/fluxcd/pkg/apis/meta v1.6.0 - github.com/fluxcd/pkg/oci v0.40.0 + github.com/fluxcd/pkg/oci v0.41.0 github.com/fluxcd/pkg/runtime v0.49.0 github.com/fluxcd/pkg/version v0.4.0 github.com/google/go-containerregistry v0.20.2 @@ -31,9 +32,9 @@ require ( require ( cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect @@ -45,21 +46,21 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ecr v1.32.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.30.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.29 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.29 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ecr v1.32.2 // indirect github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.21.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect - github.com/aws/smithy-go v1.20.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect + github.com/aws/smithy-go v1.20.4 // indirect github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230519004202-7f2db5bd753e // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect @@ -79,7 +80,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fluxcd/cli-utils v0.36.0-flux.9 // indirect - github.com/fluxcd/pkg/cache v0.0.2 // indirect + github.com/fluxcd/pkg/cache v0.0.3 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-errors/errors v1.5.1 // indirect diff --git a/go.sum b/go.sum index e8e3456b..fd8d0a0e 100644 --- a/go.sum +++ b/go.sum @@ -5,12 +5,12 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8af github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= @@ -50,49 +50,49 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= -github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= +github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= github.com/aws/aws-sdk-go-v2/config v1.18.25/go.mod h1:dZnYpD5wTW/dQF0rRNLVypB396zWCcPiBIvdvSWHEg4= -github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= -github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/config v1.27.29 h1:+ZPKb3u9Up4KZWLGTtpTmC5T3XmRD1ZQ8XQjRCHUvJw= +github.com/aws/aws-sdk-go-v2/config v1.27.29/go.mod h1:yxqvuubha9Vw8stEgNiStO+yZpP68Wm9hLmcm+R/Qk4= github.com/aws/aws-sdk-go-v2/credentials v1.13.24/go.mod h1:jYPYi99wUOPIFi0rhiOvXeSEReVOzBqFNOX5bXYoG2o= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/aws-sdk-go-v2/credentials v1.17.29 h1:CwGsupsXIlAFYuDVHv1nnK0wnxO0wZ/g1L8DSK/xiIw= +github.com/aws/aws-sdk-go-v2/credentials v1.17.29/go.mod h1:BPJ/yXV92ZVq6G8uYvbU0gSl8q94UB63nMT5ctNO38g= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/service/ecr v1.18.11/go.mod h1:Ce1q2jlNm8BVpjLaOnwnm5v2RClAbK6txwPljFzyW6c= -github.com/aws/aws-sdk-go-v2/service/ecr v1.32.0 h1:lZoKOTEQUf5Oi9qVaZM/Hb0Z6SHIwwpDjbLFOVgB2t8= -github.com/aws/aws-sdk-go-v2/service/ecr v1.32.0/go.mod h1:RhaP7Wil0+uuuhiE4FzOOEFZwkmFAk1ZflXzK+O3ptU= +github.com/aws/aws-sdk-go-v2/service/ecr v1.32.2 h1:2RjzMZp/8PXJUMqiKkDSp7RVj6inF5DpVel35THjV+I= +github.com/aws/aws-sdk-go-v2/service/ecr v1.32.2/go.mod h1:kdk+WJbHcGVbIlRQfSrKyuKkbWDdD8I9NScyS5vZ8eQ= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.16.2/go.mod h1:uHtRE7aqXNmpeYL+7Ec7LacH5zC9+w2T5MBOeEKDdu0= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.21.5 h1:PQp21GBlGNaQ+AVJAB8w2KTmLx0DkFS2fDET2Iy3+f0= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.21.5/go.mod h1:WMntdAol8KgeYsa5sDZPsRTXs4jVZIMYu0eQVVIQxnc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wINh+4UK+k/0Yo/q8= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= -github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= -github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= +github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230519004202-7f2db5bd753e h1:hli0IOU73/tNWARHav2a41uMg7arHx0Qbhgcm4bDKXI= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230519004202-7f2db5bd753e/go.mod h1:cheRroDS4qmOzi+Ue/oMHG4AV6n9F52W5QFdEKU59a0= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -144,6 +144,10 @@ github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRK github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 h1:1NyRx2f4W4WBRyg0Kys0ZbaNmDDzZ2R/C7DTi+bbsJ0= +github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380/go.mod h1:thX175TtLTzLj3p7N/Q9IiKZ7NF+p72cvL91emV0hzo= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -166,10 +170,10 @@ github.com/fluxcd/pkg/apis/event v0.10.0 h1:eMYXjMnLQ9jctPkTauuiBmEI127RjCKDf1zf github.com/fluxcd/pkg/apis/event v0.10.0/go.mod h1:pG/3gbSBLNy6YGZP2eajiyVgkEQDvva789t46PY6NFE= github.com/fluxcd/pkg/apis/meta v1.6.0 h1:93TcRpiph0OCoQh+cI+PM7E35kBW9dScuas9tWc90Dw= github.com/fluxcd/pkg/apis/meta v1.6.0/go.mod h1:ZOeHcvyVdZDC5ZOGV7YuwplIvAx6LvmpeyhfTcNZCnc= -github.com/fluxcd/pkg/cache v0.0.2 h1:+x1VCNDQbTQ5AbrOpMH3ps3NGek+qt52+6z7UjUP818= -github.com/fluxcd/pkg/cache v0.0.2/go.mod h1:Xo09Wdo2YIiqyNrQbwvp83hIzxevznsvhcy+6xFjbcM= -github.com/fluxcd/pkg/oci v0.40.0 h1:5T/Ya4f0hxx+Wl2X3EvUzunK74XMQsn4m/QS/8fFLXM= -github.com/fluxcd/pkg/oci v0.40.0/go.mod h1:2/5L+XlMgac4dgqT/s5YnFzzOgAHqUJ6FlJmLhJEqms= +github.com/fluxcd/pkg/cache v0.0.3 h1:VK5joG/p+amh5Ob+r1OFOx0cCYiswEf8mX1/J1BG7Mw= +github.com/fluxcd/pkg/cache v0.0.3/go.mod h1:UU6oFhV+mG0A5/RwIlvXhyuKlJwQEkk92jVB3vKMLtk= +github.com/fluxcd/pkg/oci v0.41.0 h1:oQh/VLv50q0+LTzbFfzjMGn7sDVykJo2dTb7GWJTHeU= +github.com/fluxcd/pkg/oci v0.41.0/go.mod h1:iWUgmFelotr2aDbCyOTiGjqn6Vx86SYOv17L8sUi7/c= github.com/fluxcd/pkg/runtime v0.49.0 h1:XldsD4C2TsfuIgku3NEQYCXFLZWDau22YqClTGUihVo= github.com/fluxcd/pkg/runtime v0.49.0/go.mod h1:0JYsoNhrBtBC4mKAuZdfrkfIqsVGAXKM/A234HuNSnk= github.com/fluxcd/pkg/version v0.4.0 h1:3F6oeIZ+ug/f7pALIBhcUhfURel37EPPOn7nsGfsnOg= diff --git a/internal/controller/imagerepository_controller.go b/internal/controller/imagerepository_controller.go index 1a557cf8..fcfce737 100644 --- a/internal/controller/imagerepository_controller.go +++ b/internal/controller/imagerepository_controller.go @@ -20,6 +20,8 @@ import ( "context" "errors" "fmt" + "net/http" + "net/url" "regexp" "sort" "strings" @@ -330,6 +332,23 @@ func (r *ImageRepositoryReconciler) setAuthOptions(ctx context.Context, obj *ima ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() + var transportOptions []func(*http.Transport) + + // Load proxy configuration. + var proxyURL *url.URL + var err error + if obj.Spec.ProxySecretRef != nil { + proxyURL, err = r.getProxyURL(ctx, obj) + if err != nil { + return nil, err + } + if proxyURL != nil { + transportOptions = append(transportOptions, func(t *http.Transport) { + t.Proxy = http.ProxyURL(proxyURL) + }) + } + } + // Configure authentication strategy to access the registry. var options []remote.Option var authSecret corev1.Secret @@ -357,7 +376,12 @@ func (r *ImageRepositoryReconciler) setAuthOptions(ctx context.Context, obj *ima default: opts = r.DeprecatedLoginOpts } - auth, authErr = login.NewManager().Login(ctx, obj.Spec.Image, ref, opts) + var managerOpts []login.Option + if proxyURL != nil { + managerOpts = append(managerOpts, login.WithProxyURL(proxyURL)) + } + manager := login.NewManager(managerOpts...) + auth, authErr = manager.Login(ctx, obj.Spec.Image, ref, opts) } if authErr != nil { // If it's not unconfigured provider error, abort reconciliation. @@ -385,20 +409,33 @@ func (r *ImageRepositoryReconciler) setAuthOptions(ctx context.Context, obj *ima } } - tr, err := secret.TransportFromKubeTLSSecret(&certSecret) + tlsConfig, err := secret.TLSConfigFromKubeTLSSecret(&certSecret) if err != nil { return nil, err } - if tr.TLSClientConfig == nil { - tr, err = secret.TransportFromSecret(&certSecret) + if tlsConfig == nil { + tlsConfig, err = secret.TLSConfigFromSecret(&certSecret) if err != nil { return nil, err } - if tr.TLSClientConfig != nil { + if tlsConfig != nil { ctrl.LoggerFrom(ctx). Info("warning: specifying TLS auth data via `certFile`/`keyFile`/`caFile` is deprecated, please use `tls.crt`/`tls.key`/`ca.crt` instead") } } + if tlsConfig != nil { + transportOptions = append(transportOptions, func(t *http.Transport) { + t.TLSClientConfig = tlsConfig + }) + } + } + + // Specify any transport options. + if len(transportOptions) > 0 { + tr := http.DefaultTransport.(*http.Transport).Clone() + for _, opt := range transportOptions { + opt(tr) + } options = append(options, remote.WithTransport(tr)) } @@ -435,6 +472,40 @@ func (r *ImageRepositoryReconciler) setAuthOptions(ctx context.Context, obj *ima return options, nil } +// getProxyURL gets the proxy configuration for the transport based on the +// specified proxy secret reference in the ImageRepository object. +func (r *ImageRepositoryReconciler) getProxyURL(ctx context.Context, obj *imagev1.ImageRepository) (*url.URL, error) { + if obj.Spec.ProxySecretRef == nil || obj.Spec.ProxySecretRef.Name == "" { + return nil, nil + } + + proxySecretName := types.NamespacedName{ + Namespace: obj.Namespace, + Name: obj.Spec.ProxySecretRef.Name, + } + var proxySecret corev1.Secret + if err := r.Get(ctx, proxySecretName, &proxySecret); err != nil { + return nil, err + } + + proxyData := proxySecret.Data + address, ok := proxyData["address"] + if !ok { + return nil, fmt.Errorf("invalid proxy secret '%s/%s': key 'address' is missing", + obj.Namespace, obj.Spec.ProxySecretRef.Name) + } + proxyURL, err := url.Parse(string(address)) + if err != nil { + return nil, fmt.Errorf("failed to parse proxy address '%s': %w", address, err) + } + user, hasUser := proxyData["username"] + password, hasPassword := proxyData["password"] + if hasUser || hasPassword { + proxyURL.User = url.UserPassword(string(user), string(password)) + } + return proxyURL, nil +} + // shouldScan takes an image repo and the time now, and returns whether // the repository should be scanned now, and how long to wait for the // next scan. It also returns the reason for the scan. diff --git a/internal/controller/imagerepository_controller_test.go b/internal/controller/imagerepository_controller_test.go index a1fc6dcd..331bddba 100644 --- a/internal/controller/imagerepository_controller_test.go +++ b/internal/controller/imagerepository_controller_test.go @@ -145,6 +145,17 @@ func TestImageRepositoryReconciler_setAuthOptions(t *testing.T) { corev1.TLSPrivateKeyKey: clientKeyPEM, } + testProxySecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-proxy-secret", + Namespace: testNamespace, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "address": []byte("http://proxy.example.com"), + }, + } + testDeprecatedTLSSecret := &corev1.Secret{} testDeprecatedTLSSecret.Name = testDeprecatedTLSSecretName testDeprecatedTLSSecret.Namespace = testNamespace @@ -249,8 +260,28 @@ func TestImageRepositoryReconciler_setAuthOptions(t *testing.T) { wantErr: true, }, { - name: "secret ref and cert secret ref", - mockObjs: []client.Object{testSecret, testTLSSecret}, + name: "proxy secret ref with existing secret", + mockObjs: []client.Object{testProxySecret}, + imageRepoSpec: imagev1.ImageRepositorySpec{ + Image: testImg, + ProxySecretRef: &meta.LocalObjectReference{ + Name: testProxySecret.Name, + }, + }, + }, + { + name: "proxy secret ref with non-existing secret", + imageRepoSpec: imagev1.ImageRepositorySpec{ + Image: testImg, + ProxySecretRef: &meta.LocalObjectReference{ + Name: "non-existing-secret", + }, + }, + wantErr: true, + }, + { + name: "secret, cert secret and proxy secret refs", + mockObjs: []client.Object{testSecret, testTLSSecret, testProxySecret}, imageRepoSpec: imagev1.ImageRepositorySpec{ Image: testImg, SecretRef: &meta.LocalObjectReference{ @@ -259,6 +290,9 @@ func TestImageRepositoryReconciler_setAuthOptions(t *testing.T) { CertSecretRef: &meta.LocalObjectReference{ Name: testTLSSecretName, }, + ProxySecretRef: &meta.LocalObjectReference{ + Name: testProxySecret.Name, + }, }, }, { @@ -498,19 +532,22 @@ func TestImageRepositoryReconciler_scan(t *testing.T) { registryServer := test.NewRegistryServer() defer registryServer.Close() + proxyAddr, proxyPort := test.NewProxy(t) + tests := []struct { name string tags []string exclusionList []string annotation string db *mockDatabase - wantErr bool + proxyURL *url.URL + wantErr string wantTags []string wantLatestTags []string }{ { name: "no tags", - wantErr: true, + wantErr: "404 Not Found", }, { name: "simple tags", @@ -519,6 +556,23 @@ func TestImageRepositoryReconciler_scan(t *testing.T) { wantTags: []string{"a", "b", "c", "d"}, wantLatestTags: []string{"d", "c", "b", "a"}, }, + { + name: "simple tags with proxy", + tags: []string{"a", "b", "c", "d"}, + db: &mockDatabase{}, + proxyURL: &url.URL{Scheme: "http", Host: proxyAddr}, + wantTags: []string{"a", "b", "c", "d"}, + wantLatestTags: []string{"d", "c", "b", "a"}, + }, + { + name: "simple tags with incorrect proxy", + tags: []string{"a", "b", "c", "d"}, + db: &mockDatabase{}, + proxyURL: &url.URL{Scheme: "http", Host: fmt.Sprintf("localhost:%d", proxyPort+1)}, + wantErr: "connection refused", + wantTags: []string{"a", "b", "c", "d"}, + wantLatestTags: []string{"d", "c", "b", "a"}, + }, { name: "simple tags, 10+", tags: []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"}, @@ -546,13 +600,13 @@ func TestImageRepositoryReconciler_scan(t *testing.T) { name: "bad exclusion pattern", tags: []string{"a"}, // Ensure repo isn't empty to prevent 404. exclusionList: []string{"[="}, - wantErr: true, + wantErr: "failed to compile regex", }, { name: "db write fails", tags: []string{"a", "b"}, db: &mockDatabase{WriteError: errors.New("fail")}, - wantErr: true, + wantErr: "failed to set tags", }, { name: "with reconcile annotation", @@ -592,8 +646,18 @@ func TestImageRepositoryReconciler_scan(t *testing.T) { opts := []remote.Option{} + if tt.proxyURL != nil { + tr := &http.Transport{Proxy: http.ProxyURL(tt.proxyURL)} + opts = append(opts, remote.WithTransport(tr)) + } + tagCount, err := r.scan(context.TODO(), repo, ref, opts) - g.Expect(err != nil).To(Equal(tt.wantErr)) + if tt.wantErr != "" { + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring(tt.wantErr)) + } else { + g.Expect(err).NotTo(HaveOccurred()) + } if err == nil { g.Expect(tagCount).To(Equal(len(tt.wantTags))) g.Expect(r.Database.Tags(imgRepo)).To(Equal(tt.wantTags)) @@ -607,6 +671,191 @@ func TestImageRepositoryReconciler_scan(t *testing.T) { } } +func TestImageRepositoryReconciler_getProxyURL(t *testing.T) { + tests := []struct { + name string + repo *imagev1.ImageRepository + objects []client.Object + wantURL string + wantErr string + }{ + { + name: "empty proxySecretRef", + repo: &imagev1.ImageRepository{ + Spec: imagev1.ImageRepositorySpec{ + ProxySecretRef: nil, + }, + }, + }, + { + name: "non-existing proxySecretRef", + repo: &imagev1.ImageRepository{ + Spec: imagev1.ImageRepositorySpec{ + ProxySecretRef: &meta.LocalObjectReference{ + Name: "non-existing", + }, + }, + }, + wantErr: "secrets \"non-existing\" not found", + }, + { + name: "missing address in proxySecretRef", + repo: &imagev1.ImageRepository{ + Spec: imagev1.ImageRepositorySpec{ + ProxySecretRef: &meta.LocalObjectReference{ + Name: "dummy", + }, + }, + }, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummy", + }, + Data: map[string][]byte{}, + }, + }, + wantErr: "invalid proxy secret '/dummy': key 'address' is missing", + }, + { + name: "invalid address in proxySecretRef", + repo: &imagev1.ImageRepository{ + Spec: imagev1.ImageRepositorySpec{ + ProxySecretRef: &meta.LocalObjectReference{ + Name: "dummy", + }, + }, + }, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummy", + }, + Data: map[string][]byte{ + "address": {0x7f}, + }, + }, + }, + wantErr: "failed to parse proxy address '\x7f': parse \"\\x7f\": net/url: invalid control character in URL", + }, + { + name: "no user, no password", + repo: &imagev1.ImageRepository{ + Spec: imagev1.ImageRepositorySpec{ + ProxySecretRef: &meta.LocalObjectReference{ + Name: "dummy", + }, + }, + }, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummy", + }, + Data: map[string][]byte{ + "address": []byte("http://proxy.example.com"), + }, + }, + }, + wantURL: "http://proxy.example.com", + }, + { + name: "user, no password", + repo: &imagev1.ImageRepository{ + Spec: imagev1.ImageRepositorySpec{ + ProxySecretRef: &meta.LocalObjectReference{ + Name: "dummy", + }, + }, + }, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummy", + }, + Data: map[string][]byte{ + "address": []byte("http://proxy.example.com"), + "username": []byte("user"), + }, + }, + }, + wantURL: "http://user:@proxy.example.com", + }, + { + name: "no user, password", + repo: &imagev1.ImageRepository{ + Spec: imagev1.ImageRepositorySpec{ + ProxySecretRef: &meta.LocalObjectReference{ + Name: "dummy", + }, + }, + }, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummy", + }, + Data: map[string][]byte{ + "address": []byte("http://proxy.example.com"), + "password": []byte("password"), + }, + }, + }, + wantURL: "http://:password@proxy.example.com", + }, + { + name: "user, password", + repo: &imagev1.ImageRepository{ + Spec: imagev1.ImageRepositorySpec{ + ProxySecretRef: &meta.LocalObjectReference{ + Name: "dummy", + }, + }, + }, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dummy", + }, + Data: map[string][]byte{ + "address": []byte("http://proxy.example.com"), + "username": []byte("user"), + "password": []byte("password"), + }, + }, + }, + wantURL: "http://user:password@proxy.example.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + c := fake.NewClientBuilder(). + WithScheme(testEnv.Scheme()). + WithObjects(tt.objects...). + Build() + + r := &ImageRepositoryReconciler{ + Client: c, + } + + u, err := r.getProxyURL(ctx, tt.repo) + if tt.wantErr == "" { + g.Expect(err).To(BeNil()) + } else { + g.Expect(err.Error()).To(ContainSubstring(tt.wantErr)) + } + if tt.wantURL == "" { + g.Expect(u).To(BeNil()) + } else { + g.Expect(u.String()).To(Equal(tt.wantURL)) + } + }) + } +} + func TestGetLatestTags(t *testing.T) { tests := []struct { name string diff --git a/internal/secret/secret.go b/internal/secret/secret.go index 41e372a1..a226a93a 100644 --- a/internal/secret/secret.go +++ b/internal/secret/secret.go @@ -23,7 +23,6 @@ import ( "encoding/json" "errors" "fmt" - "net/http" "net/url" "strings" "testing" @@ -45,54 +44,30 @@ type dockerConfig struct { Auths map[string]authn.AuthConfig } -// TransportFromSecret reads the TLS data specified in the provided Secret -// and returns a transport configured with the appropriate TLS settings. +// TLSConfigFromSecret reads the TLS data specified in the provided Secret +// and returns a tls.Config with the appropriate TLS settings. // It checks for the following keys in the Secret: // - `caFile`, for the CA certificate // - `certFile` and `keyFile`, for the certificate and private key // -// If none of these keys exists in the Secret then an empty transport is +// If none of these keys exists in the Secret then nil is // returned. If only a certificate OR private key is found, an error is // returned. -func TransportFromSecret(certSecret *corev1.Secret) (*http.Transport, error) { - // It's possible the secret doesn't contain any certs after - // all and the default transport could be used; but it's - // simpler here to assume a fresh transport is needed. - transport := &http.Transport{} - config, err := tlsConfigFromSecret(certSecret, false) - if err != nil { - return nil, err - } - if config != nil { - transport.TLSClientConfig = config - } - - return transport, nil +func TLSConfigFromSecret(certSecret *corev1.Secret) (*tls.Config, error) { + return tlsConfigFromSecret(certSecret, false) } -// TransportFromKubeTLSSecret reads the TLS data specified in the provided -// Secret and returns a transport configured with the appropriate TLS settings. +// TLSConfigFromKubeTLSSecret reads the TLS data specified in the provided +// Secret and returns a tls.Config with the appropriate TLS settings. // It checks for the following keys in the Secret: // - `ca.crt`, for the CA certificate // - `tls.crt` and `tls.key`, for the certificate and private key // -// If none of these keys exists in the Secret then an empty transport is +// If none of these keys exists in the Secret then nil is // returned. If only a certificate OR private key is found, an error is // returned. -func TransportFromKubeTLSSecret(certSecret *corev1.Secret) (*http.Transport, error) { - // It's possible the secret doesn't contain any certs after - // all and the default transport could be used; but it's - // simpler here to assume a fresh transport is needed. - transport := &http.Transport{} - config, err := tlsConfigFromSecret(certSecret, true) - if err != nil { - return nil, err - } - if config != nil { - transport.TLSClientConfig = config - } - - return transport, nil +func TLSConfigFromKubeTLSSecret(certSecret *corev1.Secret) (*tls.Config, error) { + return tlsConfigFromSecret(certSecret, true) } // tlsClientConfigFromSecret attempts to construct and return a TLS client diff --git a/internal/test/listener.go b/internal/test/listener.go new file mode 100644 index 00000000..2fdc9389 --- /dev/null +++ b/internal/test/listener.go @@ -0,0 +1,48 @@ +/* +Copyright 2024 The Flux 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 test + +import ( + "net" + "strconv" + "strings" + "testing" +) + +// NewListener creates a TCP listener on a random port and returns +// the listener, the address and the port of this listener. +// It also registers a cleanup function to close the listener +// when the test ends. +func NewListener(t *testing.T) (net.Listener, string, int) { + t.Helper() + + lis, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("failed to create listener: %v", err) + } + t.Cleanup(func() { lis.Close() }) + + addr := lis.Addr().String() + addrParts := strings.Split(addr, ":") + portStr := addrParts[len(addrParts)-1] + port, err := strconv.Atoi(portStr) + if err != nil { + t.Fatalf("failed to parse port: %v", err) + } + + return lis, addr, port +} diff --git a/internal/test/proxy.go b/internal/test/proxy.go new file mode 100644 index 00000000..11112ff3 --- /dev/null +++ b/internal/test/proxy.go @@ -0,0 +1,46 @@ +/* +Copyright 2024 The Flux 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 test + +import ( + "net/http" + "testing" + + "github.com/elazarl/goproxy" +) + +// NewProxy creates a new goproxy server on a random port and returns +// the address and the port of this server. It also registers a +// cleanup functions to close the server and the listener when +// the test ends. +func NewProxy(t *testing.T) (string, int) { + t.Helper() + + lis, addr, port := NewListener(t) + + handler := goproxy.NewProxyHttpServer() + handler.Verbose = true + + server := &http.Server{ + Addr: addr, + Handler: handler, + } + go server.Serve(lis) + t.Cleanup(func() { server.Close() }) + + return addr, port +}