diff --git a/README.md b/README.md index 5366708..d1fc1e8 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,43 @@ config: # Currently only index.yaml registry is supported (helm supports other registries as well) override: - registry: - url: "https://some.url/index.yaml" # Url to the index file + url: "https://some.url" # Url to the index file charts: # Chart names - splunk - falco-eks-audit-bridge ``` +## Configuration for password protected registries + +If the registry needs authentication then you can use a Kubernetes secret to store the username and password. + +```bash +kubectl create secret generic chartmuseum --from-literal=username=admin --from-literal=password=admin +``` + +And use following configuration: + +```yaml +# Helm configuration +config: + helmRegistries: + overrideChartNames: {} + mysql: stable/test + # If the helm charts are not stored on hub.helm.sh then a custom registry can be configured here. + # Currently only index.yaml registry is supported (helm supports other registries as well) + override: + - registry: + url: "https://some.url" # Url to the index file + secretRef: + name: "chartmuseum" # Name of the secret containing the username and password + userKey: "username" # Key of the username in the secret + passKey: "password" # Key of the password in the secret + charts: # Chart names + - splunk + - falco-eks-audit-bridge +``` + + * Query https://artifacthub.io for the chart matching your chart name and only using the specified registries. If no registry name is specified and multiple charts match from helm hub no version will be found and it will log a warning. ```yaml # Helm configuration diff --git a/example_conf.yaml b/example_conf.yaml index 7214cb0..1709b85 100644 --- a/example_conf.yaml +++ b/example_conf.yaml @@ -1,3 +1,25 @@ helmRegistries: registryNames: - - bitnami \ No newline at end of file + - bitnami + override: + - registry: + url: "http://localhost:8080/index.yaml" # Url to the index file + secretRef: + name: "chartmuseum" # Name of the secret containing the username and password + userKey: "username" # Key of the username in the secret + passKey: "password" # Key of the password in the secret + charts: # Chart names + - azure-pipelines-agent + - starboard-exporter + - registry: + url: "http://localhost:8080" # Url to the index file + secretRef: + name: "chartmuseum" # Name of the secret containing the username and password + userKey: "username" # Key of the username in the secret + passKey: "password" # Key of the password in the secret + charts: # Chart names + - starboard-operator + - registry: + url: "https://aquasecurity.github.io/helm-charts/" # Url to the index file + charts: # Chart names + - test \ No newline at end of file diff --git a/go.mod b/go.mod index c359c51..ae37d52 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/orcaman/concurrent-map v1.0.0 github.com/prometheus/client_golang v1.12.1 github.com/sirupsen/logrus v1.8.1 - gopkg.in/yaml.v2 v2.4.0 helm.sh/helm/v3 v3.8.0 k8s.io/apimachinery v0.23.3 k8s.io/client-go v0.23.3 @@ -64,6 +63,7 @@ require ( github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/swag v0.21.1 // indirect github.com/gobwas/glob v0.2.3 // indirect + github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.2.0 // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -140,6 +140,7 @@ require ( google.golang.org/grpc v1.44.0 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/api v0.23.3 // indirect k8s.io/apiextensions-apiserver v0.23.3 // indirect diff --git a/go.sum b/go.sum index aafb34b..7cfefc9 100644 --- a/go.sum +++ b/go.sum @@ -516,8 +516,9 @@ github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= diff --git a/helm/values.yaml b/helm/values.yaml index 6d8f3cc..5a7dae5 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -109,7 +109,11 @@ config: # Currently only index.yaml registry is supported (helm supports other registries as well) override: - registry: - url: "" # https://some.url/index.yaml # Url to the index file + url: "" # https://some.url # Url to the index file + # secretRef: + # name: "chartmuseum" # Name of the secret containing the username and password + # userKey: "username" # Key of the username in the secret + # passKey: "password" # Key of the password in the secret charts: [] # Chart names # - splunk # - falco-eks-audit-bridge diff --git a/main.go b/main.go index 01f5fd5..8c50d5e 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,7 @@ import ( "sync" "time" - semver "github.com/Masterminds/semver" + "github.com/Masterminds/semver" "github.com/sstarcher/helm-exporter/config" diff --git a/registries/charts.go b/registries/charts.go index a10d09a..0484eee 100644 --- a/registries/charts.go +++ b/registries/charts.go @@ -22,9 +22,17 @@ type HelmOverrideRegistry struct { AllowAllReleases bool `koanf:"allowAllReleases"` } +// SecretRef contains information about a secret +type SecretRef struct { + Name string `yaml:"name"` + UserKey string `yaml:"userKey"` + PassKey string `yaml:"passKey"` +} + // HelmRegistry contains information about the helm registry type HelmRegistry struct { - URL string `koanf:"url"` + URL string `koanf:"url"` + SecretRef *SecretRef `koanf:"secretRef"` } // GetLatestVersionFromHelm fetches the latest version of the helm chart diff --git a/registries/helm_index.go b/registries/helm_index.go index 0be8971..4cc58ff 100644 --- a/registries/helm_index.go +++ b/registries/helm_index.go @@ -1,12 +1,18 @@ package registries import ( - "net/http" - - "gopkg.in/yaml.v2" + "context" + "github.com/sstarcher/helm-exporter/versioning" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/repo" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "os" + "strings" log "github.com/sirupsen/logrus" - "github.com/sstarcher/helm-exporter/versioning" ) // IndexEntries contains configured Helm indexes @@ -20,29 +26,71 @@ type IndexEntry struct { Version string `yaml:"version"` } -func (r HelmOverrideRegistry) getChartVersions(chart string) string { - resp, err := http.Get(r.HelmRegistry.URL) +const indexYamlSuffix = "/index.yaml" + +var clientSet kubernetes.Interface +var settings *cli.EnvSettings + +func init() { + settings = cli.New() + actionConfig := new(action.Configuration) + if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), log.Printf); err != nil { + log.Warning(err) + } + + var err error + clientSet, err = actionConfig.KubernetesClientSet() if err != nil { - log.WithError(err).WithField("chart", chart).WithField("registry", r.HelmRegistry.URL).Error("Failed to get chart info") + log.Warning(err) + } +} + +func (r HelmOverrideRegistry) getChartVersions(chart string) string { + + // trim the index.yaml suffix from the chart url, just to avoid breaking changes. + url := strings.TrimSuffix(r.HelmRegistry.URL, indexYamlSuffix) + + entry := &repo.Entry{ + Name: chart, + URL: url, + } + + if clientSet == nil { + log.Warning("kubernetes ClientSet is not initialized") return versioning.Failure } - defer resp.Body.Close() - index := IndexEntries{} - err = yaml.NewDecoder(resp.Body).Decode(&index) + if r.HelmRegistry.SecretRef != nil { + secrets, err := clientSet.CoreV1().Secrets(settings.Namespace()).Get(context.Background(), r.HelmRegistry.SecretRef.Name, v1.GetOptions{}) + if err != nil { + log.Warning(err) + return versioning.Failure + } + entry.Username = string(secrets.Data[r.HelmRegistry.SecretRef.UserKey]) + entry.Password = string(secrets.Data[r.HelmRegistry.SecretRef.PassKey]) + } + + provider := getter.All(settings) + + chartRepo, err := repo.NewChartRepository(entry, provider) if err != nil { - log.WithError(err).WithField("chart", chart).WithField("registry", r.HelmRegistry.URL).Error("Failed to unmarshal chart info") + log.Warning(err) return versioning.Failure } - - var versions []string - entries := index.Entries[chart] - if entries == nil { - return versioning.Notfound + idx, err := chartRepo.DownloadIndexFile() + if err != nil { + log.Warning(err) + return versioning.Failure } - for _, entry := range entries { - versions = append(versions, entry.Version) + repoIndex, err := repo.LoadIndexFile(idx) + if err != nil { + log.Warning(err) + return versioning.Failure } - - return versioning.FindHighestVersionInList(versions, r.AllowAllReleases) + chartVersion, err := repoIndex.Get(chart, "") + if err != nil { + log.Warning(err) + return versioning.Failure + } + return chartVersion.Version }