Skip to content

Commit

Permalink
✨ Add support for specifying TLS config including custom CA certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
jimmidyson committed May 23, 2024
1 parent 904bc72 commit ffb2dc2
Show file tree
Hide file tree
Showing 12 changed files with 318 additions and 50 deletions.
3 changes: 3 additions & 0 deletions api/v1alpha1/condition_consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,7 @@ const (

// GetCredentialsFailedReason indicates that the HelmReleaseProxy failed to get the credentials for the Helm registry.
GetCredentialsFailedReason = "GetCredentialsFailed"

// GetCACertificateFailedReason indicates that the HelmReleaseProxy failed to get the CA certiicate for the Helm registry.
GetCACertificateFailedReason = "GetCACertificateFailed"
)
15 changes: 15 additions & 0 deletions api/v1alpha1/helmchartproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ type HelmChartProxySpec struct {
// Credentials is a reference to an object containing the OCI credentials. If it is not specified, no credentials will be used.
// +optional
Credentials *Credentials `json:"credentials,omitempty"`

// TLSConfig contains the TLS configuration for a HelmChartProxy.
// +optional
TLSConfig *TLSConfig `json:"tlsConfig,omitempty"`
}

type HelmOptions struct {
Expand Down Expand Up @@ -199,6 +203,17 @@ type Credentials struct {
Key string `json:"key"`
}

// TLSConfig defines a TLS configuration.
type TLSConfig struct {
// Secret is a reference to a Secret containing the TLS CA certificate at the key ca.crt.
// +optional
CASecretRef *corev1.SecretReference `json:"caSecret,omitempty"`

// InsecureSkipTLSVerify controls whether the Helm client should verify the server's certificate.
// +optional
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
}

// HelmChartProxyStatus defines the observed state of HelmChartProxy.
type HelmChartProxyStatus struct {
// Conditions defines current state of the HelmChartProxy.
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha1/helmreleaseproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ type HelmReleaseProxySpec struct {
// Credentials is a reference to an object containing the OCI credentials. If it is not specified, no credentials will be used.
// +optional
Credentials *Credentials `json:"credentials,omitempty"`

// TLSConfig contains the TLS configuration for the HelmReleaseProxy.
TLSConfig *TLSConfig `json:"tlsConfig,omitempty"`
}

// HelmReleaseProxyStatus defines the observed state of HelmReleaseProxy.
Expand Down
30 changes: 30 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions config/crd/bases/addons.cluster.x-k8s.io_helmchartproxies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,28 @@ spec:
RepoURL is the URL of the Helm chart repository.
e.g. chart-path oci://repo-url/chart-name as repoURL: oci://repo-url and https://repo-url/chart-name as repoURL: https://repo-url
type: string
tlsConfig:
description: TLSConfig contains the TLS configuration for a HelmChartProxy.
properties:
caSecret:
description: Secret is a reference to a Secret containing the
TLS CA certificate at the key ca.crt.
properties:
name:
description: name is unique within a namespace to reference
a secret resource.
type: string
namespace:
description: namespace defines the space within which the
secret name must be unique.
type: string
type: object
x-kubernetes-map-type: atomic
insecureSkipTLSVerify:
description: InsecureSkipTLSVerify controls whether the Helm client
should verify the server's certificate.
type: boolean
type: object
valuesTemplate:
description: |-
ValuesTemplate is an inline YAML representing the values for the Helm chart. This YAML supports Go templating to reference
Expand Down
22 changes: 22 additions & 0 deletions config/crd/bases/addons.cluster.x-k8s.io_helmreleaseproxies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,28 @@ spec:
RepoURL is the URL of the Helm chart repository.
e.g. chart-path oci://repo-url/chart-name as repoURL: oci://repo-url and https://repo-url/chart-name as repoURL: https://repo-url
type: string
tlsConfig:
description: TLSConfig contains the TLS configuration for the HelmReleaseProxy.
properties:
caSecret:
description: Secret is a reference to a Secret containing the
TLS CA certificate at the key ca.crt.
properties:
name:
description: name is unique within a namespace to reference
a secret resource.
type: string
namespace:
description: namespace defines the space within which the
secret name must be unique.
type: string
type: object
x-kubernetes-map-type: atomic
insecureSkipTLSVerify:
description: InsecureSkipTLSVerify controls whether the Helm client
should verify the server's certificate.
type: boolean
type: object
values:
description: |-
Values is an inline YAML representing the values for the Helm chart. This YAML is the result of the rendered
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,15 @@ func constructHelmReleaseProxy(existing *addonsv1alpha1.HelmReleaseProxy, helmCh
}
}

helmReleaseProxy.Spec.TLSConfig = helmChartProxy.Spec.TLSConfig

if helmReleaseProxy.Spec.TLSConfig != nil {
// If the namespace is not set, set it to the namespace of the HelmChartProxy
if helmReleaseProxy.Spec.TLSConfig.CASecretRef.Namespace == "" {
helmReleaseProxy.Spec.TLSConfig.CASecretRef.Namespace = helmChartProxy.Namespace
}
}

return helmReleaseProxy
}

Expand Down
91 changes: 83 additions & 8 deletions controllers/helmreleaseproxy/helmreleaseproxy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,21 +224,39 @@ func (r *HelmReleaseProxyReconciler) Reconcile(ctx context.Context, req ctrl.Req
return ctrl.Result{}, wrappedErr
}

defer func() {
if err := os.Remove(credentialsPath); err != nil {
log.Error(err, "failed to remove credentials file in path", "credentialsPath", credentialsPath)
}
}()
if credentialsPath != "" {
defer func() {
if err := os.Remove(credentialsPath); err != nil {
log.Error(err, "failed to remove credentials file in path", "credentialsPath", credentialsPath)
}
}()
}

caFilePath, err := r.getCAFile(ctx, helmReleaseProxy)
if err != nil {
wrappedErr := errors.Wrapf(err, "failed to get CA certificate file for cluster")
conditions.MarkFalse(helmReleaseProxy, addonsv1alpha1.ClusterAvailableCondition, addonsv1alpha1.GetCACertificateFailedReason, clusterv1.ConditionSeverityError, wrappedErr.Error())

return ctrl.Result{}, wrappedErr
}

if caFilePath != "" {
defer func() {
if err := os.Remove(caFilePath); err != nil {
log.Error(err, "failed to remove CA certificate file in path", "credentialsPath", caFilePath)
}
}()
}

log.V(2).Info("Reconciling HelmReleaseProxy", "releaseProxyName", helmReleaseProxy.Name)
err = r.reconcileNormal(ctx, helmReleaseProxy, r.HelmClient, credentialsPath, kubeconfig)
err = r.reconcileNormal(ctx, helmReleaseProxy, r.HelmClient, credentialsPath, caFilePath, kubeconfig)

return ctrl.Result{}, err
}

// reconcileNormal handles HelmReleaseProxy reconciliation when it is not being deleted. This will install or upgrade the HelmReleaseProxy on the Cluster.
// It will set the ReleaseName on the HelmReleaseProxy if the name is generated and also set the release status and release revision.
func (r *HelmReleaseProxyReconciler) reconcileNormal(ctx context.Context, helmReleaseProxy *addonsv1alpha1.HelmReleaseProxy, client internal.Client, credentialsPath string, kubeconfig string) error {
func (r *HelmReleaseProxyReconciler) reconcileNormal(ctx context.Context, helmReleaseProxy *addonsv1alpha1.HelmReleaseProxy, client internal.Client, credentialsPath, caFilePath string, kubeconfig string) error {
log := ctrl.LoggerFrom(ctx)

log.V(2).Info("Reconciling HelmReleaseProxy on cluster", "HelmReleaseProxy", helmReleaseProxy.Name, "cluster", helmReleaseProxy.Spec.ClusterRef.Name)
Expand All @@ -250,7 +268,7 @@ func (r *HelmReleaseProxyReconciler) reconcileNormal(ctx context.Context, helmRe
})
}

release, err := client.InstallOrUpgradeHelmRelease(ctx, kubeconfig, credentialsPath, helmReleaseProxy.Spec)
release, err := client.InstallOrUpgradeHelmRelease(ctx, kubeconfig, credentialsPath, caFilePath, helmReleaseProxy.Spec)
if err != nil {
log.Error(err, fmt.Sprintf("Failed to install or upgrade release '%s' on cluster %s", helmReleaseProxy.Spec.ReleaseName, helmReleaseProxy.Spec.ClusterRef.Name))
conditions.MarkFalse(helmReleaseProxy, addonsv1alpha1.HelmReleaseReadyCondition, addonsv1alpha1.HelmInstallOrUpgradeFailedReason, clusterv1.ConditionSeverityError, err.Error())
Expand Down Expand Up @@ -377,6 +395,31 @@ func (r *HelmReleaseProxyReconciler) getCredentials(ctx context.Context, helmRel
return credentialsPath, nil
}

// getCAFile fetches the CA certificate from a Secret and writes it to a temporary file, returning the path to the temporary file.
func (r *HelmReleaseProxyReconciler) getCAFile(ctx context.Context, helmReleaseProxy *addonsv1alpha1.HelmReleaseProxy) (string, error) {
caFilePath := ""
if helmReleaseProxy.Spec.TLSConfig != nil && helmReleaseProxy.Spec.TLSConfig.CASecretRef.Name != "" {
// By default, the secret is in the same namespace as the HelmReleaseProxy
if helmReleaseProxy.Spec.TLSConfig.CASecretRef.Namespace == "" {
helmReleaseProxy.Spec.TLSConfig.CASecretRef.Namespace = helmReleaseProxy.Namespace
}
caSecretValues, err := r.getCACertificateFromSecret(ctx, helmReleaseProxy.Spec.TLSConfig.CASecretRef.Name, helmReleaseProxy.Spec.TLSConfig.CASecretRef.Namespace)
if err != nil {
return "", err
}

// Write to a file
filename, err := writeCACertificateToFile(ctx, caSecretValues)
if err != nil {
return "", err
}

caFilePath = filename
}

return caFilePath, nil
}

// getCredentialsFromSecret returns the OCI credentials from a Secret.
func (r *HelmReleaseProxyReconciler) getCredentialsFromSecret(ctx context.Context, name, namespace, key string) ([]byte, error) {
secret := &corev1.Secret{}
Expand Down Expand Up @@ -407,3 +450,35 @@ func writeCredentialsToFile(ctx context.Context, credentials []byte) (string, er

return credentialsFile.Name(), nil
}

// getCredentialsFromSecret returns the OCI credentials from a Secret.
func (r *HelmReleaseProxyReconciler) getCACertificateFromSecret(ctx context.Context, name, namespace string) ([]byte, error) {
secret := &corev1.Secret{}
if err := r.Client.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, secret); err != nil {
return nil, err
}

const key = "ca.crt"
credentials, ok := secret.Data[key]
if !ok {
return nil, errors.New(fmt.Sprintf("key %s not found in secret %s/%s", key, namespace, name))
}

return credentials, nil
}

// writeCACertificateToFile writes the CA certificate to a temporary file.
func writeCACertificateToFile(ctx context.Context, caCertificate []byte) (string, error) {
log := ctrl.LoggerFrom(ctx)
log.V(2).Info("Writing CA certficate to file")
caCertFile, err := os.CreateTemp("", "ca-*.crt")
if err != nil {
return "", err
}

if _, err := caCertFile.Write(caCertificate); err != nil {
return "", err
}

return caCertFile.Name(), nil
}
Loading

0 comments on commit ffb2dc2

Please sign in to comment.