Merge pull request #9 from fluxcd/helm-auth
Helm repository and chart HTTP and TLS auth
This commit is contained in:
commit
b7897b7144
|
@ -23,11 +23,16 @@ import (
|
||||||
|
|
||||||
// HelmRepositorySpec defines the desired state of HelmRepository
|
// HelmRepositorySpec defines the desired state of HelmRepository
|
||||||
type HelmRepositorySpec struct {
|
type HelmRepositorySpec struct {
|
||||||
// The repository address
|
// The Helm repository URL, a valid URL contains at least a
|
||||||
// +kubebuilder:validation:MinLength=4
|
// protocol and host.
|
||||||
// +required
|
// +required
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
|
|
||||||
|
// The name of the secret containing authentication credentials
|
||||||
|
// for the Helm repository.
|
||||||
|
// +optional
|
||||||
|
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
|
||||||
|
|
||||||
// The interval at which to check for repository updates
|
// The interval at which to check for repository updates
|
||||||
// +required
|
// +required
|
||||||
Interval metav1.Duration `json:"interval"`
|
Interval metav1.Duration `json:"interval"`
|
||||||
|
|
|
@ -281,7 +281,7 @@ func (in *HelmRepository) DeepCopyInto(out *HelmRepository) {
|
||||||
*out = *in
|
*out = *in
|
||||||
out.TypeMeta = in.TypeMeta
|
out.TypeMeta = in.TypeMeta
|
||||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
out.Spec = in.Spec
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
in.Status.DeepCopyInto(&out.Status)
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,6 +338,11 @@ func (in *HelmRepositoryList) DeepCopyObject() runtime.Object {
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *HelmRepositorySpec) DeepCopyInto(out *HelmRepositorySpec) {
|
func (in *HelmRepositorySpec) DeepCopyInto(out *HelmRepositorySpec) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.SecretRef != nil {
|
||||||
|
in, out := &in.SecretRef, &out.SecretRef
|
||||||
|
*out = new(v1.LocalObjectReference)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
out.Interval = in.Interval
|
out.Interval = in.Interval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,18 @@ spec:
|
||||||
interval:
|
interval:
|
||||||
description: The interval at which to check for repository updates
|
description: The interval at which to check for repository updates
|
||||||
type: string
|
type: string
|
||||||
|
secretRef:
|
||||||
|
description: The name of the secret containing authentication credentials
|
||||||
|
for the Helm repository.
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||||
|
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
url:
|
url:
|
||||||
description: The repository address
|
description: The Helm repository URL, a valid URL contains at least
|
||||||
minLength: 4
|
a protocol and host.
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- interval
|
- interval
|
||||||
|
|
|
@ -140,7 +140,7 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
|
||||||
auth, err := r.auth(repository, tmpSSH)
|
auth, err := r.auth(repository, tmpSSH)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("auth error: %w", err)
|
err = fmt.Errorf("auth error: %w", err)
|
||||||
return sourcev1.GitRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create tmp dir for the Git clone
|
// create tmp dir for the Git clone
|
||||||
|
|
|
@ -36,6 +36,7 @@ import (
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
||||||
|
"github.com/fluxcd/source-controller/internal/helm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HelmChartReconciler reconciles a HelmChart object
|
// HelmChartReconciler reconciles a HelmChart object
|
||||||
|
@ -155,7 +156,30 @@ func (r *HelmChartReconciler) sync(repository sourcev1.HelmRepository, chart sou
|
||||||
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
|
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.Get(u.String(), getter.WithURL(repository.Spec.URL))
|
var clientOpts []getter.Option
|
||||||
|
if repository.Spec.SecretRef != nil {
|
||||||
|
name := types.NamespacedName{
|
||||||
|
Namespace: repository.GetNamespace(),
|
||||||
|
Name: repository.Spec.SecretRef.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
var secret corev1.Secret
|
||||||
|
err := r.Client.Get(context.TODO(), name, &secret)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("auth secret error: %w", err)
|
||||||
|
return sourcev1.HelmChartNotReady(chart, sourcev1.AuthenticationFailedReason, err.Error()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts, cleanup, err := helm.ClientOptionsFromSecret(secret)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("auth options error: %w", err)
|
||||||
|
return sourcev1.HelmChartNotReady(chart, sourcev1.AuthenticationFailedReason, err.Error()), err
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
clientOpts = opts
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.Get(u.String(), clientOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
|
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,13 @@ import (
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
||||||
|
"github.com/fluxcd/source-controller/internal/helm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HelmRepositoryReconciler reconciles a HelmRepository object
|
// HelmRepositoryReconciler reconciles a HelmRepository object
|
||||||
|
@ -113,9 +115,30 @@ func (r *HelmRepositoryReconciler) sync(repository sourcev1.HelmRepository) (sou
|
||||||
u.RawPath = path.Join(u.RawPath, "index.yaml")
|
u.RawPath = path.Join(u.RawPath, "index.yaml")
|
||||||
u.Path = path.Join(u.Path, "index.yaml")
|
u.Path = path.Join(u.Path, "index.yaml")
|
||||||
|
|
||||||
indexURL := u.String()
|
var clientOpts []getter.Option
|
||||||
// TODO(hidde): add authentication config
|
if repository.Spec.SecretRef != nil {
|
||||||
res, err := c.Get(indexURL, getter.WithURL(repository.Spec.URL))
|
name := types.NamespacedName{
|
||||||
|
Namespace: repository.GetNamespace(),
|
||||||
|
Name: repository.Spec.SecretRef.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
var secret corev1.Secret
|
||||||
|
err := r.Client.Get(context.TODO(), name, &secret)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("auth secret error: %w", err)
|
||||||
|
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts, cleanup, err := helm.ClientOptionsFromSecret(secret)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("auth options error: %w", err)
|
||||||
|
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
clientOpts = opts
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.Get(u.String(), clientOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.IndexationFailedReason, err.Error()), err
|
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.IndexationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
@ -162,14 +185,14 @@ func (r *HelmRepositoryReconciler) sync(repository sourcev1.HelmRepository) (sou
|
||||||
}
|
}
|
||||||
|
|
||||||
// update index symlink
|
// update index symlink
|
||||||
indexUrl, err := r.Storage.Symlink(artifact, "index.yaml")
|
indexURL, err := r.Storage.Symlink(artifact, "index.yaml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("storage error: %w", err)
|
err = fmt.Errorf("storage error: %w", err)
|
||||||
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
message := fmt.Sprintf("Helm repository index is available at: %s", artifact.Path)
|
message := fmt.Sprintf("Helm repository index is available at: %s", artifact.Path)
|
||||||
return sourcev1.HelmRepositoryReady(repository, artifact, indexUrl, sourcev1.IndexationSucceededReason, message), nil
|
return sourcev1.HelmRepositoryReady(repository, artifact, indexURL, sourcev1.IndexationSucceededReason, message), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HelmRepositoryReconciler) shouldResetStatus(repository sourcev1.HelmRepository) (bool, sourcev1.HelmRepositoryStatus) {
|
func (r *HelmRepositoryReconciler) shouldResetStatus(repository sourcev1.HelmRepository) (bool, sourcev1.HelmRepositoryStatus) {
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
package helm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"helm.sh/helm/v3/pkg/getter"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ClientOptionsFromSecret(secret corev1.Secret) ([]getter.Option, func(), error) {
|
||||||
|
var opts []getter.Option
|
||||||
|
basicAuth, err := BasicAuthFromSecret(secret)
|
||||||
|
if err != nil {
|
||||||
|
return opts, nil, err
|
||||||
|
}
|
||||||
|
opts = append(opts, basicAuth)
|
||||||
|
tlsClientConfig, cleanup, err := TLSClientConfigFromSecret(secret)
|
||||||
|
if err != nil {
|
||||||
|
return opts, nil, err
|
||||||
|
}
|
||||||
|
opts = append(opts, tlsClientConfig)
|
||||||
|
return opts, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BasicAuthFromSecret(secret corev1.Secret) (getter.Option, error) {
|
||||||
|
username, password := string(secret.Data["username"]), string(secret.Data["password"])
|
||||||
|
switch {
|
||||||
|
case username == "" && password == "":
|
||||||
|
return nil, nil
|
||||||
|
case username == "" || password == "":
|
||||||
|
return nil, fmt.Errorf("invalid '%s' secret data: required fields 'username' and 'password'", secret.Name)
|
||||||
|
}
|
||||||
|
return getter.WithBasicAuth(username, password), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TLSClientConfigFromSecret(secret corev1.Secret) (getter.Option, func(), error) {
|
||||||
|
certBytes, keyBytes, caBytes := secret.Data["certFile"], secret.Data["keyFile"], secret.Data["caFile"]
|
||||||
|
switch {
|
||||||
|
case len(certBytes)+len(keyBytes)+len(caBytes) == 0:
|
||||||
|
return nil, nil, nil
|
||||||
|
case len(certBytes) == 0 || len(keyBytes) == 0 || len(caBytes) == 0:
|
||||||
|
return nil, nil, fmt.Errorf("invalid '%s' secret data: required fields 'certFile', 'keyFile' and 'caFile'",
|
||||||
|
secret.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create tmp dir for TLS files
|
||||||
|
tmp, err := ioutil.TempDir("", "helm-tls-"+secret.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
cleanup := func() { os.RemoveAll(tmp) }
|
||||||
|
|
||||||
|
certFile := filepath.Join(tmp, "cert.crt")
|
||||||
|
if err := ioutil.WriteFile(certFile, certBytes, 0644); err != nil {
|
||||||
|
cleanup()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
keyFile := filepath.Join(tmp, "key.crt")
|
||||||
|
if err := ioutil.WriteFile(keyFile, keyBytes, 0644); err != nil {
|
||||||
|
cleanup()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
caFile := filepath.Join(tmp, "ca.pem")
|
||||||
|
if err := ioutil.WriteFile(caFile, caBytes, 0644); err != nil {
|
||||||
|
cleanup()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return getter.WithTLSClientConfig(certFile, keyFile, caFile), cleanup, nil
|
||||||
|
}
|
Loading…
Reference in New Issue