Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3cac34c
Use CA in cloudprofile for STACKIT clients
nschad May 21, 2026
1d346d4
Add CABundle to mcm-provider-stackit if specified
nschad Jun 5, 2026
3fc27c4
deduplicate CABundle for CSI/CCM
nschad Jun 5, 2026
3951f6d
align volumeName with CSI/CCM
nschad Jun 5, 2026
1247bc8
fix some lint errors
nschad Jun 8, 2026
155c48e
drop costuom-certs in provider-stackit
nschad Jun 8, 2026
402ffcd
do not double-encode cloudprofile caBundle
nschad Jun 8, 2026
36d70d0
fix secret chart
nschad Jun 8, 2026
0e8fe95
decode cloudProfile.caBundle before using it
nschad Jun 8, 2026
521eb06
use stackit SDK WithCustomHTTPClient for custom Transport with modifi…
nschad Jun 8, 2026
0cb5e9c
format and simplify code
nschad Jun 8, 2026
5b01a22
remove unused helm value
nschad Jun 8, 2026
980bdbf
rename InjectCAIntoHTTPClient => newHTTPClientWithCustomCA
nschad Jun 8, 2026
74fff99
fix: use newObj instead of old machineDeployment
nschad Jun 9, 2026
5b75cf3
add unit test for webhook
nschad Jun 9, 2026
059d603
add unit test for DecodeCloudProfileCABundle
nschad Jun 9, 2026
232f787
use const variable instead of string for volume name
nschad Jun 9, 2026
be9ae1a
rename cloudProfileCABundleB64 => CABundleBase64
nschad Jun 9, 2026
d8a0ca0
remove stackitv1alpha1.Cloudprofile's CABundle; use gardener's CABund…
nschad Jun 9, 2026
aa88233
Gardener's Cloudprofile CABundle is not encoded. Encode it for the he…
nschad Jun 9, 2026
de1feea
use EnsureVolumeWithName and EnsureVolumeMountWithName instead of app…
nschad Jun 15, 2026
e85a96e
add stackit-ca-bundle from config chart to internal chart
nschad Jun 15, 2026
588e65e
use EnsureEnvVarWithName for the ENV vars
nschad Jun 15, 2026
a966878
keep caBundle in the Cloudprofile to not breaking strict decoding
nschad Jun 24, 2026
bcdf4ae
improve error message for newHTTPClientWithCustomCA()
nschad Jul 3, 2026
85951a0
rename stackit-ca-bundle to cloudprofile-ca-bundle
nschad Jul 3, 2026
d1bc758
use constant for cloudprofile-ca-bundle
nschad Jul 3, 2026
40db20c
remove the mcm flag --resource-exhausted-retry as this version was re…
nschad Jul 3, 2026
04db709
remove gosec nolint directive
nschad Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,6 @@ spec:
volumeMounts:
- name: config
mountPath: /etc/{{ include "name" . }}/config
- name: custom-certs
mountPath: /etc/ssl/certs/customca.crt
readOnly: true
subPath: customca.crt
{{- if .Values.imageVectorOverwrite }}
- name: imagevector-overwrite
mountPath: /charts_overwrite/
Expand All @@ -144,10 +140,6 @@ spec:
configMap:
name: {{ include "name" . }}-configmap
defaultMode: 420
- name: custom-certs
Comment thread
breuerfelix marked this conversation as resolved.
secret:
secretName: {{ include "name" . }}-ca
optional: true
{{- if .Values.imageVectorOverwrite }}
- name: imagevector-overwrite
configMap:
Expand Down

This file was deleted.

3 changes: 0 additions & 3 deletions charts/gardener-extension-provider-stackit/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@ config:
capacity: 25Gi
provisioner: block-storage.csi.stackit.cloud
volumeBindingMode: WaitForFirstConsumer
stackitAPIEndpoints:
# Must be base64 encoded
caBundle: ""
registryCaches:
# - server: reg.example.com
# cache: reg-cache.example.com
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{- if .Values.CABundle }}
apiVersion: v1
kind: Secret
metadata:
name: cloudprofile-ca-bundle
namespace: {{ .Release.Namespace }}
type: Opaque
data:
{{- if .Values.CABundle }}
stackit-ca.crt: {{ .Values.CABundle | b64enc }}
{{- end }}
{{- end }}
1 change: 1 addition & 0 deletions charts/internal/cloud-provider-config/values.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
stackitOnly: false
CABundle: ""
# [Global]
authUrl: fooURL
domainName: fooDomain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,9 @@ spec:
mountPath: {{ .Values.socketPath }}
- name: cloud-provider-config
mountPath: /etc/config
# Host certificates are mounted to accommodate OpenStack endpoints that might be served with a certificate
# signed by a CA that is not globally trusted.
- mountPath: /usr/share/ca-certificates
name: usr-share-ca-certificates
readOnly: true
- mountPath: /etc/ssl
name: etc-ssl
- mountPath: /etc/ssl/certs/stackit-ca.crt
name: stackit-ca
subPath: stackit-ca.crt
readOnly: true
securityContext:
allowPrivilegeEscalation: false
Expand Down Expand Up @@ -344,15 +340,11 @@ spec:
path: token
name: shoot-access-csi-resizer
optional: false
- name: usr-share-ca-certificates
hostPath:
path: /usr/share/ca-certificates
- name: cloud-provider-config
configMap:
defaultMode: 420
name: {{ .Values.prefix }}-cloud-provider-config
# Host certificates are mounted to accommodate OpenStack endpoints that might be served with a certificate
# signed by a CA that is not globally trusted.
- name: etc-ssl
hostPath:
path: /etc/ssl
- name: stackit-ca
secret:
optional: true
secretName: cloudprofile-ca-bundle
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ spec:
name: config-volume
- mountPath: /etc/serviceaccount
name: sa-secret
- mountPath: /etc/ssl/certs/keystone-ca.crt
name: cloud-config-ca
subPath: keystone-ca.crt
- mountPath: /etc/ssl/certs/stackit-ca.crt
name: stackit-ca
subPath: stackit-ca.crt
readOnly: true
securityContext:
allowPrivilegeEscalation: false
Expand All @@ -111,9 +111,9 @@ spec:
items:
- key: "serviceaccount.json"
path: "serviceaccount.json"
- name: cloud-config-ca
- name: stackit-ca
secret:
secretName: cloud-provider-config
secretName: cloudprofile-ca-bundle
optional: true
- name: kubeconfig
projected:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ config:
stackitProjectID: foo
stackitNetworkID: foo
stackitRegion: foo
#loadBalancerApiUrl: foo.bar.example.com/v1
#iaasApiUrl: foo.bar.example.com/v1
#tokenUrl: foo.bar.example.com/v1
loadBalancerApiUrl: ""
iaasApiUrl: ""
tokenUrl: ""
loadBalancerEmergencyToken: ""
port: 10258
metricsPort: 9090
podAnnotations: {}
Expand Down
2 changes: 1 addition & 1 deletion hack/api-reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ string
</td>
<td>
<em>(Optional)</em>
<p>CABundle is the CA certificate bundle for API endpoints.<br />This field is currently ignored and reserved for future use.</p>
<p>CABundle is the CA certificate bundle for API endpoints.<br />This field is currently ignored and reserved for future use.<br />Deprecated: Will be removed soon. This field is not used anymore</p>
</td>
</tr>
<tr>
Expand Down
5 changes: 0 additions & 5 deletions pkg/admission/mutator/namespacedcloudprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,6 @@ func (p *namespacedCloudProfile) Mutate(_ context.Context, newObj, _ client.Obje
statusConfig.APIEndpoints = specConfig.APIEndpoints
}

// Overwrite CA bundle from spec
if specConfig.CABundle != nil {
statusConfig.CABundle = specConfig.CABundle
}

modifiedStatusConfig, err := json.Marshal(statusConfig)
if err != nil {
return err
Expand Down
19 changes: 0 additions & 19 deletions pkg/admission/mutator/namespacedcloudprofile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,25 +96,6 @@ var _ = Describe("NamespacedCloudProfile Mutator", func() {
Expect(mergedConfig.APIEndpoints.LoadBalancer).To(PointTo(Equal("https://custom-lb.example.com")))
})

It("should correctly merge caBundle from spec", func() {
namespacedCloudProfile.Status.CloudProfileSpec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{
"apiVersion":"stackit.provider.extensions.gardener.cloud/v1alpha1",
"kind":"CloudProfileConfig",
"machineImages":[
{"name":"image-1","versions":[{"version":"1.0","image":"image-name-1"}]}
]}`)}
namespacedCloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{
"apiVersion":"stackit.provider.extensions.gardener.cloud/v1alpha1",
"kind":"CloudProfileConfig",
"caBundle":"-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----"}`)}

Expect(namespacedCloudProfileMutator.Mutate(ctx, namespacedCloudProfile, nil)).To(Succeed())

mergedConfig, err := helper.CloudProfileConfigFromRawExtension(namespacedCloudProfile.Status.CloudProfileSpec.ProviderConfig)
Expect(err).ToNot(HaveOccurred())
Expect(mergedConfig.CABundle).To(PointTo(Equal("-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----")))
})

It("should correctly merge extended machineImages", func() {
namespacedCloudProfile.Status.CloudProfileSpec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{
"apiVersion":"stackit.provider.extensions.gardener.cloud/v1alpha1",
Expand Down
3 changes: 1 addition & 2 deletions pkg/admission/validator/namespacedcloudprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,11 @@ func (p *namespacedCloudProfile) validateNamespacedCloudProfileProviderConfig(pr
},
MachineImages: providerConfig.MachineImages,
APIEndpoints: providerConfig.APIEndpoints,
CABundle: providerConfig.CABundle,
}
if !equality.Semantic.DeepEqual(validationProviderConfig, providerConfig) {
allErrs = append(allErrs, field.Forbidden(
field.NewPath("spec.providerConfig"),
"must only set machineImages, stackitAPIEndpoints, and stackitCABundle",
"must only set machineImages and stackitAPIEndpoints",
))
}

Expand Down
17 changes: 2 additions & 15 deletions pkg/admission/validator/namespacedcloudprofile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ var _ = Describe("NamespacedCloudProfile Validator", func() {
Expect(err).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{
"Type": Equal(field.ErrorTypeForbidden),
"Field": Equal("spec.providerConfig"),
"Detail": Equal("must only set machineImages, stackitAPIEndpoints, and stackitCABundle"),
"Detail": Equal("must only set machineImages and stackitAPIEndpoints"),
}))))
})

Expand All @@ -297,24 +297,11 @@ var _ = Describe("NamespacedCloudProfile Validator", func() {
Expect(namespacedCloudProfileValidator.Validate(ctx, namespacedCloudProfile, nil)).To(Succeed())
})

It("should succeed for NamespacedCloudProfile specifying only caBundle", func() {
namespacedCloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{
"apiVersion":"stackit.provider.extensions.gardener.cloud/v1alpha1",
"kind":"CloudProfileConfig",
"caBundle":"-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----"
}`)}

Expect(fakeClient.Create(ctx, cloudProfile)).To(Succeed())

Expect(namespacedCloudProfileValidator.Validate(ctx, namespacedCloudProfile, nil)).To(Succeed())
})

It("should succeed for NamespacedCloudProfile specifying apiEndpoints, caBundle, and machineImages together", func() {
It("should succeed for NamespacedCloudProfile specifying apiEndpoints, and machineImages together", func() {
namespacedCloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{
"apiVersion":"stackit.provider.extensions.gardener.cloud/v1alpha1",
"kind":"CloudProfileConfig",
"apiEndpoints":{"iaas":"https://custom-iaas.example.com"},
"caBundle":"-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----",
"machineImages":[
{"name":"image-1","versions":[{"version":"1.0","image":"image-name-1","regions":[{"name":"image-region-1","id":"id-img-reg-1"}]}]}
]
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/stackit/v1alpha1/types_cloudprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type CloudProfileConfig struct {
APIEndpoints *APIEndpoints `json:"apiEndpoints,omitempty"`
// CABundle is the CA certificate bundle for API endpoints.
// This field is currently ignored and reserved for future use.
//
// Deprecated: Will be removed soon. This field is not used anymore
// +optional
CABundle *string `json:"caBundle,omitempty"`
Comment thread
nschad marked this conversation as resolved.
// Bastion are the bastion options for configuring the bastion server
Expand Down
11 changes: 11 additions & 0 deletions pkg/controller/controlplane/valuesprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ var (
Objects: []*chart.Object{
{Type: &corev1.Secret{}, Name: openstack.CloudProviderConfigName},
{Type: &corev1.Secret{}, Name: openstack.CloudProviderDiskConfigName},
{Type: &corev1.Secret{}, Name: openstack.CloudProfileCASecretName},
},
}

Expand Down Expand Up @@ -681,6 +682,16 @@ func getConfigChartValues(
}
}

// Deploy the cloudprofile-ca-bundle when at least one of stackit-mcm, stackit-csi or stackit-ccm is deployed
if feature.UseStackitMachineControllerManager(cluster) ||
getCSIDriver(controlPlaneConfig) == stackitv1alpha1.STACKIT ||
getCCMController(controlPlaneConfig) == stackitv1alpha1.STACKIT {
if cluster.CloudProfile != nil && cluster.CloudProfile.Spec.CABundle != nil {
caBundle := ptr.Deref(cluster.CloudProfile.Spec.CABundle, "")
values["CABundle"] = caBundle
}
}

return values, nil
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/openstack/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const (
// DNSCABundle is a constant for the key in a DNS secret that holds the Openstack CA Bundle for the KeyStone server.
DNSCABundle = "OS_CACERT"

// CloudProfileCASecretName is the name of the secret containing the CloudPorifle caBundle
CloudProfileCASecretName = "cloudprofile-ca-bundle"
// CloudProviderConfigName is the name of the secret containing the cloud provider config.
CloudProviderConfigName = "cloud-provider-config"
// CloudProviderDiskConfigName is the name of the secret containing the cloud provider config for disk/volume handling. It is used by kube-controller-manager.
Expand Down
8 changes: 6 additions & 2 deletions pkg/stackit/client/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import (
"github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/stackit"
)

func NewDNSClient(_ context.Context, endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials) (DNSClient, error) {
options := clientOptions(endpoints, credentials)
func NewDNSClient(_ context.Context, endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials, caBundle string) (DNSClient, error) {
options, err := clientOptions(endpoints, credentials, caBundle)
if err != nil {
return nil, err
}

if endpoints.DNS != nil {
options = append(options, sdkconfig.WithEndpoint(*endpoints.DNS))
Expand All @@ -24,6 +27,7 @@ func NewDNSClient(_ context.Context, endpoints stackitv1alpha1.APIEndpoints, cre
if err != nil {
return nil, err
}

return &dnsClient{
api: apiClient.DefaultAPI,
projectID: credentials.ProjectID,
Expand Down
47 changes: 42 additions & 5 deletions pkg/stackit/client/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package client

import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"

extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller"
sdkconfig "github.com/stackitcloud/stackit-sdk-go/core/config"
Expand Down Expand Up @@ -33,18 +37,25 @@ type Factory interface {
type factory struct {
StackitRegion string
StackitAPIEndpoints stackitv1alpha1.APIEndpoints
CABundleB64 string
}

func New(region string, cluster *extensionscontroller.Cluster) Factory {
var apiEndpoints stackitv1alpha1.APIEndpoints
var caBundle string

if cloudProfileConfig, err := helper.CloudProfileConfigFromCluster(cluster); err == nil {
apiEndpoints = ptr.Deref(cloudProfileConfig.APIEndpoints, stackitv1alpha1.APIEndpoints{})
}

if cluster.CloudProfile != nil && cluster.CloudProfile.Spec.CABundle != nil {
caBundle = ptr.Deref(cluster.CloudProfile.Spec.CABundle, "")
}

return &factory{
StackitRegion: region,
StackitAPIEndpoints: apiEndpoints,
CABundleB64: caBundle,
}
}

Expand All @@ -54,7 +65,7 @@ func (f factory) LoadBalancing(ctx context.Context, c client.Client, secretRef c
return nil, err
}

return NewLoadBalancingClient(ctx, f.StackitRegion, f.StackitAPIEndpoints, credentials)
return NewLoadBalancingClient(ctx, f.StackitRegion, f.StackitAPIEndpoints, credentials, f.CABundleB64)
}

func (f factory) IaaS(ctx context.Context, c client.Client, secretRef corev1.SecretReference) (IaaSClient, error) {
Expand All @@ -63,7 +74,7 @@ func (f factory) IaaS(ctx context.Context, c client.Client, secretRef corev1.Sec
return nil, err
}

return NewIaaSClient(f.StackitRegion, f.StackitAPIEndpoints, credentials)
return NewIaaSClient(f.StackitRegion, f.StackitAPIEndpoints, credentials, f.CABundleB64)
}

func (f factory) DNS(ctx context.Context, c client.Client, secretRef corev1.SecretReference) (DNSClient, error) {
Expand All @@ -72,10 +83,28 @@ func (f factory) DNS(ctx context.Context, c client.Client, secretRef corev1.Secr
return nil, err
}

return NewDNSClient(ctx, f.StackitAPIEndpoints, credentials)
return NewDNSClient(ctx, f.StackitAPIEndpoints, credentials, f.CABundleB64)
}

// newHTTPClientWithCustomCA creates an http.Client with a custom CA
func newHTTPClientWithCustomCA(caBundle []byte) (*http.Client, error) {
caCertPool, err := x509.SystemCertPool()
if err != nil {
// we could also fall back here and use an empty pool via x509.NewCertPool()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think erroring is fine but we should customize the error to state what is broken and where does it come from.

return nil, fmt.Errorf("failed to load system cert pool: %w", err)
}
if ok := caCertPool.AppendCertsFromPEM(caBundle); !ok {
return nil, fmt.Errorf("failed to append CA bundle to cert pool")
}

return &http.Client{Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
},
}}, nil
}

func clientOptions(endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials) []sdkconfig.ConfigurationOption {
func clientOptions(endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials, caBundle string) ([]sdkconfig.ConfigurationOption, error) {
result := []sdkconfig.ConfigurationOption{
sdkconfig.WithUserAgent(UserAgent),
sdkconfig.WithServiceAccountKey(credentials.SaKeyJSON),
Expand All @@ -85,5 +114,13 @@ func clientOptions(endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.
result = append(result, sdkconfig.WithTokenEndpoint(*endpoints.TokenEndpoint))
}

return result
if caBundle != "" {
customHttpClient, err := newHTTPClientWithCustomCA([]byte(caBundle))
if err != nil {
return nil, err
}
result = append(result, sdkconfig.WithHTTPClient(customHttpClient))
}

return result, nil
}
Loading