Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HELM-342: Add basic authentication support for Helm repositories #11782

Merged
merged 1 commit into from Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/helm/README.md
Expand Up @@ -3,3 +3,5 @@
## [Helm Endpoints API](endpoints_api.md)

## [Configure Access to 3rd Party Chart Repositories](configure-3rdparty-repos-access.md)

## [Configure Namespace-scoped Helm Chart Repositories Using Namespaced CRDs](configure-namespaced-helm-repos.md)
54 changes: 50 additions & 4 deletions docs/helm/configure-namespaced-helm-repos.md
@@ -1,8 +1,8 @@
# Configure Namespace-scoped Helm Chart Repositories Using Namespaced CRDs

The cluster-scoped `[HelmChartRepository](https://github.com/openshift/api/blob/master/helm/v1beta1/0000_10-helm-chart-repository.crd.yaml)` CRD for Helm repository provides the ability for admins to add Helm repositories as custom resources. For helm chart repositories that support tls authentication the tls configuration secret has to be created in `openshift-config` namespace.
The cluster-scoped [HelmChartRepository](https://github.com/openshift/api/blob/master/helm/v1beta1/0000_10-helm-chart-repository.crd.yaml) custom resource definition (CRD) for Helm repository provides the ability for admins to add Helm repositories as custom resources (CRs). For Helm chart repositories that support TLS authentication, the TLS configuration secret has to be created in the `openshift-config` namespace.

The namespace-scoped `[ProjectHelmChartRepository](https://github.com/openshift/api/blob/master/helm/v1beta1/0000_10-project-chart-repository.crd.yaml)` CRD allows project members with the appropriate RBAC permissions to create Helm repository resources of their choice but scoped to their namespace. Such project members can see charts from both cluster-scoped and namespace-scoped Helm repository resources (CRs). For namespace-scoped helm chart repositories that support tls authentication the tls configuration secret and certificate authority config map must be created in the namespace where the repository is getting instantiated.
The namespace-scoped `[ProjectHelmChartRepository](https://github.com/openshift/api/blob/master/helm/v1beta1/0000_10-project-chart-repository.crd.yaml)` CRD allows project members with the appropriate RBAC permissions to create Helm repository resources of their choice but scoped to their namespace. Such project members can see charts from both cluster-scoped and namespace-scoped Helm repository resources (CRs). For namespace-scoped Helm chart repositories that support TLS authentication, the TLS configuration secret and certificate authority (CA) config map must be created in the namespace where the repository is getting instantiated. The namespace-scoped Helm chart repositories also support basic authentication. A user can create a secret with a username and password in order to add authentication for their repository.

_Note_: Administrators can limit users from creating namespace-scoped Helm repository resources. By limiting users, admins have the flexibility to control the RBAC through a namespace role instead of a cluster role. Doing so avoids unnecessary permission elevation for the user and prevents access to unauthorized services or applications.

Expand All @@ -25,15 +25,16 @@ spec:
# required: chart repository URL
# optional: tlsClientConfig is an optional reference to a secret by name that contains the PEM-encoded TLS client certificate and private key to present when connecting to the server. The key "tls.crt" is used to locate the client certificate. The key "tls.key" is used to locate the private key. The namespace for this secret must be same as the namespace where the project helm chart repository is getting instantiated.
# optional: ca is an optional reference to a config map by name containing the PEM-encoded CA bundle. It is used as a trust anchor to validate the TLS certificate presented by the remote server. The key "ca-bundle.crt" is used to locate the data. If empty, the default system roots are used. The namespace for this configmap must be same as the namespace where the project helm chart repository is getting instantiated.
connectionConfig:
# optional: basicAuthConfig is an optional reference to a secret by name that contains the basic authentication credentials to be present when connecting to the server. The key "username" is used to locate the username. The key "password" is used to locate the password. The namespace for this secret must be the same as the namespace where the project helm chart repository is getting instantiated.
url: HELM_CHART_REPO_URL
Copy link
Member

Choose a reason for hiding this comment

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

@Kartikey-star Thanks for adding this back.

tlsClientConfig: HELM_CHART_TLS_CLIENT_CONFIG_SECRET_NAME
ca: HELM_CHART_CA_CONFIG_MAP_NAME
basicAuthConfig: HELM_CHART_BASIC_AUTH_SECRET_NAME
```

## Adding Namespace-scoped Custom Helm Chart Repositories

To add a new namespace-scoped Helm repository, add a custom Helm Chart Repository CR, for example, `azure-sample-repo` CR to your `my-namespace` namespace:
1. To add a new namespace-scoped Helm repository, add a custom Helm Chart Repository CR, for example, `azure-sample-repo` CR to your `my-namespace` namespace:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
1. To add a new namespace-scoped Helm repository, add a custom Helm Chart Repository CR, for example, `azure-sample-repo` CR to your `my-namespace` namespace:
- To add a new namespace-scoped Helm repository, add a custom Helm Chart Repository CR, for example, `azure-sample-repo` CR to your `my-namespace` namespace:

A single step must be just as a bullet item.


```bash
$ cat <<EOF | kubectl apply --namespace my-namespace -f -
Expand All @@ -46,14 +47,59 @@ spec:
connectionConfig:
url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docs
EOF
```

2. Verify that the `ProjectHelmChartRepository` CR is added successfully:

```
$ kubectl get projecthelmchartrepositories --namespace my-namespace
NAME AGE
azure-sample-repo 1m
```

The addition of the namespace-scoped Helm repository does not impact the behavior of the existing cluster-scoped Helm repository.

## Adding Namespace-scoped Custom Helm Chart Repositories Supporting Basic Authentication

1. To create a project helm chart repository that supports basic authentication, create a secret with `username` and `password` in the same namespace where the repository is getting instantiated, for example, in the `my-namespace` namespace:

```bash
$ cat <<EOF | kubectl apply --namespace my-namespace
-f -
apiVersion: v1
kind: Secret
metadata:
name: basic-secret
stringData:
username: AzureDiamond
password: hunter2
EOF
```

2. To add a new namespace-scoped Helm repository that supports basic authentication, add a custom Helm Chart Repository CR, for example, `azure-sample-repo` CR to your `my-namespace` namespace. Also, reference the secret created, in the `basicAuthConfig` field of the `ProjectHelmChartRepository` CR:

Copy link
Member

Choose a reason for hiding this comment

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

Suggestions:

  • put 3 backticks ``` at L68.
  • Leave a line space.
  • AT L70: You must reference the secret created, in the basicAuthConfig field of the ProjectHelmChartRepository CR.
  • Leave a line space.

Copy link
Member

Choose a reason for hiding this comment

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

Add this step here.

```bash
$ cat <<EOF | kubectl apply --namespace my-namespace -f -
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
$ cat <<EOF | kubectl apply --namespace my-namespace -f -
- At L73, leave a line space
- At L74, add: ```bash
- At L75: $ cat <<EOF | kubectl apply --namespace my-namespace -f -

apiVersion: helm.openshift.io/v1beta1
kind: ProjectHelmChartRepository
metadata:
name: azure-sample-repo
spec:
name: azure-sample-repo
connectionConfig:
url: https://raw.githubusercontent.com/Azure-Samples/helm-charts/master/docs
basicAuthConfig: basic-secret
EOF
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
EOF
EOF

```

3. Verify that the `ProjectHelmChartRepository` CR is added successfully:

Copy link
Member

Choose a reason for hiding this comment

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

  1. Verify that the ProjectHelmChartRepository CR is added successfully:

```
$ kubectl get projecthelmchartrepositories --namespace my-namespace
NAME AGE
azure-sample-repo 1m
```

## API Endpoint `/api/helm/charts/index.yaml`

The `/api/helm/charts/index.yaml` endpoint supports a `namespace` query parameter. For example, a GET request to `/api/helm/charts/index.yaml?namespace=my-namespace` will respond an aggregated `index.yaml` file with entities extracted from both cluster-scoped and namespace-scoped Helm repositories.
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -11,7 +11,7 @@ require (
github.com/devfile/registry-support/registry-library v0.0.0-20220527155645-8328a8a883be
github.com/gorilla/websocket v1.4.2
github.com/graph-gophers/graphql-go v0.0.0-20200309224638-dae41bde9ef9
github.com/openshift/api v0.0.0-20220729142910-83d1191dd9fc
github.com/openshift/api v0.0.0-20220803132145-8e34324aa580
github.com/openshift/library-go v0.0.0-20200424095618-2aeb4725dadf
github.com/operator-framework/kubectl-operator v0.3.0
github.com/prometheus/client_golang v1.12.1
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Expand Up @@ -1070,6 +1070,8 @@ github.com/openshift/api v0.0.0-20211103080632-8981c8822dfa h1:b0MKH+EOOHNV7Vw96
github.com/openshift/api v0.0.0-20211103080632-8981c8822dfa/go.mod h1:RsQCVJu4qhUawxxDP7pGlwU3IA4F01wYm3qKEu29Su8=
github.com/openshift/api v0.0.0-20220729142910-83d1191dd9fc h1:qxkHuIwh4BpI8R/TOZelF/oeyddh0whMFkPCZRhnPro=
github.com/openshift/api v0.0.0-20220729142910-83d1191dd9fc/go.mod h1:LEnw1IVscIxyDnltE3Wi7bQb/QzIM8BfPNKoGA1Qlxw=
github.com/openshift/api v0.0.0-20220803132145-8e34324aa580 h1:cd9zktm0qiXUdSgmFoqrhxgPweih3KrAqaxCCGwq4go=
github.com/openshift/api v0.0.0-20220803132145-8e34324aa580/go.mod h1:LEnw1IVscIxyDnltE3Wi7bQb/QzIM8BfPNKoGA1Qlxw=
github.com/openshift/build-machinery-go v0.0.0-20200211121458-5e3d6e570160/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc=
github.com/openshift/build-machinery-go v0.0.0-20200424080330-082bf86082cc/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc=
github.com/openshift/build-machinery-go v0.0.0-20200819073603-48aa266c95f7/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE=
Expand Down Expand Up @@ -1986,7 +1988,6 @@ k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8=
k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=
k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=
k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg=
k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY=
k8s.io/api v0.24.0 h1:J0hann2hfxWr1hinZIDefw7Q96wmCBx6SSB8IY0MdDg=
k8s.io/api v0.24.0/go.mod h1:5Jl90IUrJHUJYEMANRURMiVvJ0g7Ax7r3R1bqO8zx8I=
k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
Expand All @@ -2011,7 +2012,6 @@ k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRp
k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=
k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI=
k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/apimachinery v0.24.0 h1:ydFCyC/DjCvFCHK5OPMKBlxayQytB8pxy8YQInd5UyQ=
k8s.io/apimachinery v0.24.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
Expand Down Expand Up @@ -2052,7 +2052,6 @@ k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NI
k8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg=
k8s.io/code-generator v0.20.2/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg=
k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo=
k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
k8s.io/code-generator v0.24.0/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w=
k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
k8s.io/component-base v0.16.7/go.mod h1:ikdyfezOFMu5O0qJjy/Y9eXwj+fV3pVwdmt0ulVcIR0=
Expand Down Expand Up @@ -2089,7 +2088,6 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc=
k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-aggregator v0.18.0/go.mod h1:ateewQ5QbjMZF/dihEFXwaEwoA4v/mayRvzfmvb6eqI=
Expand Down
19 changes: 19 additions & 0 deletions pkg/helm/actions/auth.go
Expand Up @@ -44,6 +44,7 @@ func setUpAuthentication(chartPathOptions *action.ChartPathOptions, connectionCo

func setUpAuthenticationProject(chartPathOptions *action.ChartPathOptions, connectionConfig *v1beta1.ConnectionConfigNamespaceScoped, coreClient corev1client.CoreV1Interface, namespace string) ([]*os.File, error) {
tlsFiles := []*os.File{}
var secretNamespace string
//set up tls cert and key
if connectionConfig.TLSClientConfig != (configv1.SecretNameReference{}) {
chartPathOptions.RepoURL = connectionConfig.URL
Expand All @@ -56,6 +57,24 @@ func setUpAuthenticationProject(chartPathOptions *action.ChartPathOptions, conne
chartPathOptions.KeyFile = tlsKeyFile.Name()
tlsFiles = append(tlsFiles, tlsKeyFile)
}
//set up basic auth
if connectionConfig.BasicAuthConfig != (configv1.SecretNameReference{}) {
secretName := connectionConfig.BasicAuthConfig.Name
secret, err := coreClient.Secrets(namespace).Get(context.TODO(), secretName, v1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to GET secret '%s/%s', reason %v", secretNamespace, secretName, err)
}
baUsername, found := secret.Data[username]
if !found {
return nil, fmt.Errorf("failed to find %q key in secret '%s/%s'", username, secretNamespace, secretName)
}
chartPathOptions.Username = string(baUsername)
baPassword, found := secret.Data[password]
if !found {
return nil, fmt.Errorf("failed to find %q key in secret '%s/%s'", password, secretNamespace, secretName)
}
chartPathOptions.Password = string(baPassword)
}
//set up ca certificate
if connectionConfig.CA != (configv1.ConfigMapNameReference{}) {
chartPathOptions.RepoURL = connectionConfig.URL
Expand Down
119 changes: 119 additions & 0 deletions pkg/helm/actions/get_chart_test.go
Expand Up @@ -253,6 +253,125 @@ func TestGetChartWithTlsData(t *testing.T) {
}
}

func TestGetChartBasicAuth(t *testing.T) {
tests := []struct {
name string
chartPath string
chartName string
indexEntry string
repositoryNamespace string
createSecret bool
createNamespace bool
namespace string
requireError bool
helmCRS []*unstructured.Unstructured
}{
{
name: "mychart",
chartPath: "http://localhost:8181/charts/mychart-0.1.0.tgz",
chartName: "mychart",
createSecret: true,
createNamespace: true,
namespace: "test",
indexEntry: "mychart--my-repo",
helmCRS: []*unstructured.Unstructured{
{
Object: map[string]interface{}{
"apiVersion": "helm.openshift.io/v1beta1",
"kind": "ProjectHelmChartRepository",
"metadata": map[string]interface{}{
"namespace": "test",
"name": "my-repo",
},
"spec": map[string]interface{}{
"connectionConfig": map[string]interface{}{
"url": "http://localhost:8181",
"basicAuthConfig": map[string]interface{}{
"name": "my-repo",
},
},
},
},
},
},
},
{
name: "Invalid chart url",
chartPath: "http://localhost:8181/charts/mychart-0.2.0.tgz",
chartName: "mychart",
createSecret: true,
createNamespace: true,
namespace: "test",
indexEntry: "mychart--my-repo",
requireError: true,
helmCRS: []*unstructured.Unstructured{
{
Object: map[string]interface{}{
"apiVersion": "helm.openshift.io/v1beta1",
"kind": "ProjectHelmChartRepository",
"metadata": map[string]interface{}{
"namespace": "test",
"name": "my-repo",
},
"spec": map[string]interface{}{
"connectionConfig": map[string]interface{}{
"url": "http://localhost:8181",
"basicAuthConfig": map[string]interface{}{
"name": "my-repo",
},
},
},
},
},
},
},
{
name: "Invalid chart url",
chartPath: "../testdata/invalid.tgz",
requireError: true,
},
}
store := storage.Init(driver.NewMemory())
actionConfig := &action.Configuration{
RESTClientGetter: FakeConfig{},
Releases: store,
KubeClient: &kubefake.PrintingKubeClient{Out: ioutil.Discard},
Capabilities: chartutil.DefaultCapabilities,
Log: func(format string, v ...interface{}) {},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
objs := []runtime.Object{}
// create a namespace if it is not same as openshift-config
if test.createNamespace {
nsSpec := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: test.namespace}}
objs = append(objs, nsSpec)
}
// create a secret in required namespace
if test.createSecret {
data := map[string][]byte{
username: []byte("AzureDiamond"),
password: []byte("hunter2"),
}
secretSpec := &v1.Secret{Data: data, ObjectMeta: metav1.ObjectMeta{Name: "my-repo", Namespace: test.namespace}}
objs = append(objs, secretSpec)
}

client := K8sDynamicClientFromCRs(test.helmCRS...)
Copy link
Member

Choose a reason for hiding this comment

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

doesnt the ProjectHelmChartRepository resource have its own client ?

Copy link
Author

Choose a reason for hiding this comment

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

I'm pretty sure it does, but the initial implementation was already using the dynamic client would modify the code more than initially wanted, so we kept it for the time being.

clientInterface := k8sfake.NewSimpleClientset(objs...)
coreClient := clientInterface.CoreV1()
chart, err := GetChart(test.chartPath, actionConfig, test.namespace, client, coreClient, false, test.indexEntry)
if test.requireError {
require.Error(t, err)
} else {
require.NoError(t, err)
require.NotNil(t, chart.Metadata)
require.Equal(t, chart.Metadata.Name, test.chartName)
}
})
}
}

func K8sDynamicClientFromCRs(crs ...*unstructured.Unstructured) dynamic.Interface {
var objs []runtime.Object

Expand Down