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
|
||||
type HelmRepositorySpec struct {
|
||||
// The repository address
|
||||
// +kubebuilder:validation:MinLength=4
|
||||
// The Helm repository URL, a valid URL contains at least a
|
||||
// protocol and host.
|
||||
// +required
|
||||
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
|
||||
// +required
|
||||
Interval metav1.Duration `json:"interval"`
|
||||
|
|
|
@ -281,7 +281,7 @@ func (in *HelmRepository) DeepCopyInto(out *HelmRepository) {
|
|||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
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.
|
||||
func (in *HelmRepositorySpec) DeepCopyInto(out *HelmRepositorySpec) {
|
||||
*out = *in
|
||||
if in.SecretRef != nil {
|
||||
in, out := &in.SecretRef, &out.SecretRef
|
||||
*out = new(v1.LocalObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
out.Interval = in.Interval
|
||||
}
|
||||
|
||||
|
|
|
@ -52,9 +52,18 @@ spec:
|
|||
interval:
|
||||
description: The interval at which to check for repository updates
|
||||
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:
|
||||
description: The repository address
|
||||
minLength: 4
|
||||
description: The Helm repository URL, a valid URL contains at least
|
||||
a protocol and host.
|
||||
type: string
|
||||
required:
|
||||
- interval
|
||||
|
|
|
@ -140,7 +140,7 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
|
|||
auth, err := r.auth(repository, tmpSSH)
|
||||
if err != nil {
|
||||
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
|
||||
|
|
|
@ -36,6 +36,7 @@ import (
|
|||
"sigs.k8s.io/yaml"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
||||
"github.com/fluxcd/source-controller/internal/helm"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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 {
|
||||
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err
|
||||
}
|
||||
|
|
|
@ -30,11 +30,13 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
||||
"github.com/fluxcd/source-controller/internal/helm"
|
||||
)
|
||||
|
||||
// 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.Path = path.Join(u.Path, "index.yaml")
|
||||
|
||||
indexURL := u.String()
|
||||
// TODO(hidde): add authentication config
|
||||
res, err := c.Get(indexURL, 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.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 {
|
||||
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.IndexationFailedReason, err.Error()), err
|
||||
}
|
||||
|
@ -162,14 +185,14 @@ func (r *HelmRepositoryReconciler) sync(repository sourcev1.HelmRepository) (sou
|
|||
}
|
||||
|
||||
// update index symlink
|
||||
indexUrl, err := r.Storage.Symlink(artifact, "index.yaml")
|
||||
indexURL, err := r.Storage.Symlink(artifact, "index.yaml")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("storage error: %w", err)
|
||||
return sourcev1.HelmRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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