Skip to content

Commit

Permalink
add readme, support auth-token
Browse files Browse the repository at this point in the history
Signed-off-by: xh4n3 <xyn1016@gmail.com>
  • Loading branch information
xh4n3 committed Jan 13, 2020
1 parent 971384c commit e46d1c2
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 172 deletions.
202 changes: 41 additions & 161 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,179 +1,59 @@
# helm push plugin
<img align="right" src="https://github.com/helm/chartmuseum/raw/master/logo.png">
# helm acr

[![Codefresh build status]( https://g.codefresh.io/api/badges/pipeline/chartmuseum/chartmuseum%2Fhelm-push%2Fmaster?type=cf-1)]( https://g.codefresh.io/public/accounts/chartmuseum/pipelines/chartmuseum/helm-push/master)
[![CircleCI](https://circleci.com/gh/AliyunContainerService/helm-acr.svg?style=svg)](https://circleci.com/gh/AliyunContainerService/helm-acr)
[![Go Report Card](https://goreportcard.com/badge/github.com/AliyunContainerService/helm-acr)](https://goreportcard.com/report/github.com/AliyunContainerService/helm-acr)

Helm plugin to push chart package to [ChartMuseum](https://github.com/helm/chartmuseum)
Helm plugin to push chart package to [ChartMuseum](https://github.com/helm/chartmuseum).

## Install
Based on the version in `plugin.yaml`, release binary will be downloaded from GitHub:
This project is forked from [chartmuseum/helm-push](https://github.com/chartmuseum/helm-push).

```
$ helm plugin install https://github.com/chartmuseum/helm-push
Downloading and installing helm-push v0.8.1 ...
https://github.com/chartmuseum/helm-push/releases/download/v0.8.1/helm-push_0.8.1_darwin_amd64.tar.gz
Installed plugin: push
```

## Usage
Start by adding a ChartMuseum-backed repo via Helm CLI (if not already added)
```
$ helm repo add chartmuseum http://localhost:8080
```
For all available plugin options, please run
```
$ helm push --help
```

### Pushing a directory
Point to a directory containing a valid `Chart.yaml` and the chart will be packaged and uploaded:
```
$ cat mychart/Chart.yaml
name: mychart
version: 0.3.2
```
```
$ helm push mychart/ chartmuseum
Pushing mychart-0.3.2.tgz to chartmuseum...
Done.
```

### Pushing with a custom version
The `--version` flag can be provided, which will push the package with a custom version.

Here is an example using the last git commit id as the version:
```
$ helm push mychart/ --version="$(git log -1 --pretty=format:%h)" chartmuseum
Pushing mychart-5abbbf28.tgz to chartmuseum...
Done.
```
If you want to enable something like `--version="latest"`, which you intend to push regularly, you will need to run your ChartMuseum server with `ALLOW_OVERWRITE=true`.

### Push .tgz package
This workflow does not require the use of `helm package`, but pushing .tgzs is still suppported:
```
$ helm push mychart-0.3.2.tgz chartmuseum
Pushing mychart-0.3.2.tgz to chartmuseum...
Done.
```

### Force push
If your ChartMuseum install is configured with `ALLOW_OVERWRITE=true`, chart versions will be automatically overwritten upon re-upload.

Otherwise, unless your install is configured with `DISABLE_FORCE_OVERWRITE=true` (ChartMuseum > v0.7.1), you can use the `--force`/`-f` option to to force an upload:
```
$ helm push --force mychart-0.3.2.tgz chartmuseum
Pushing mychart-0.3.2.tgz to chartmuseum...
Done.
```

### Pushing directly to URL
If the second argument provided resembles a URL, you are not required to add the repo prior to push:
```
$ helm push mychart-0.3.2.tgz http://localhost:8080
Pushing mychart-0.3.2.tgz to http://localhost:8080...
Done.
```

## Context Path

If you are running ChartMuseum behind a proxy that adds a route prefix, for example:
```
https://my.chart.repo.com/helm/v1/index.yaml -> http://chartmuseum-svc/index.yaml
```

You can use the `--context-path=` option or `HELM_REPO_CONTEXT_PATH` env var in order for the plugin to construct the upload URL correctly:
```
helm repo add chartmuseum https://my.chart.repo.com/helm/v1
helm push --context-path=/helm/v1 mychart-0.3.2.tgz chartmuseum
```

Alternatively, you can add `serverInfo.contextPath` to your index.yaml:
```
apiVersion: v1
entries:{}
generated: "2018-08-09T11:08:21-05:00"
serverInfo:
contextPath: /helm/v1
```

In ChartMuseum server (>0.7.1) this will automatically be added to index.yaml if the `--context-path` option is provided.
Some modifications has been made to meet the security requirements on Alibaba Cloud:
* the plugin is able to talk to auth server to gain a Bearer Token.
* the plugin is able to use the Bearer Token to download/upload charts to Chartmuseum.
* the plugin registers `acr`(short for Alibaba Cloud Container Registry) as protocol name in `plugin.yaml`.

## Authentication
### Basic Auth
If you have added your repo with the `--username`/`--password` flags (Helm 2.9+), or have added your repo with the basic auth username/password in the URL (e.g. `https://myuser:mypass@my.chart.repo.com`), no further setup is required.
### Installation

The plugin will use the auth info located in `~/.helm/repository/repositories.yaml` in order to authenticate.
```bash
# make sure you have git installed
yum install -y git

If you are running ChartMuseum with `AUTH_ANONYMOUS_GET=true`, and have added your repo without authentication, the plugin recognizes the following environment variables for basic auth on push operations:
# install plugin
helm plugin install https://github.com/AliyunContainerService/helm-acr
```
$ export HELM_REPO_USERNAME="myuser"
$ export HELM_REPO_PASSWORD="mypass"
```

With this setup, you can enable people to use your repo for installing charts etc. without allowing them to upload to it.

### Token
### Usage

*ChartMuseum token-auth is currently in progress. Pleasee see [auth-server-example](https://github.com/chartmuseum/auth-server-example) for more info.*
Before you use Alibaba Cloud Container Registry's hosted Helm charts service, you should:
* purchase an ACR Enterprise Edition instance and activate its Helm charts service
* have a Kubernetes cluster and have `helm init` done
* make sure you have Internet access to GitHub to download plugin
* create a Helm chart namespace in your ACR Enterprise Edition

Although ChartMuseum server does not define or accept a token format (yet), if you are running it behind a proxy that accepts access tokens, you can provide the following env var:
```
$ export HELM_REPO_ACCESS_TOKEN="<token>"
```
```bash
# add namespace/repo to your local repository
# please change username/password/namespace/repo/url below
export HELM_REPO_USERNAME=username; export HELM_REPO_PASSWORD=password;
helm repo add demo acr://hello-acr-helm.cn-hangzhou.cr.aliyuncs.com/foo/bar --username ${HELM_REPO_USERNAME} --password ${HELM_REPO_PASSWORD}

This will result in all basic auth options above being ignored, and the plugin will send the token in the header:
```
Authorization: Bearer <token>
```
# create an empty chart locally
helm create hello-acr

If you require a custom header to be used for passing the token, you can the following env var:
```
$ export HELM_REPO_AUTH_HEADER="<myheader>"
```
# push the chart
helm push hello-acr demo

This will then be used in place of `Authorization: Bearer`:
```
<myheader>: <token>
```
# delete local chart
rm -r hello-acr

#### Token config file (~/.cfconfig)
For users of [Managed Helm Repositories](https://codefresh.io/codefresh-news/introducing-managed-helm-repositories/) (Codefresh), the plugin is able to auto-detect your API key from `~/.cfconfig`. This file is managed by [Codefresh CLI](https://codefresh-io.github.io/cli/).
# update charts index from remote
helm repo update

If detected, this API key will be used for token-based auth, overriding basic auth options described above.
# show all remote charts
helm search

The format of this file is the following:
# fetch the chart we uploaded
helm fetch demo/hello-acr

```
contexts:
default:
name: default
token: <token>
current-context: default
```

### TLS Client Cert Auth

ChartMuseum server does not yet have options to setup TLS client cert authentication (please see [chartmuseum#79](https://github.com/helm/chartmuseum/issues/79)).

If you are running ChartMuseum behind a frontend that does, the following options are available:

```
--ca-file string Verify certificates of HTTPS-enabled servers using this CA bundle [$HELM_REPO_CA_FILE]
--cert-file string Identify HTTPS client using this SSL certificate file [$HELM_REPO_CERT_FILE]
--key-file string Identify HTTPS client using this SSL key file [$HELM_REPO_KEY_FILE]
--insecure Connect to server with an insecure way by skipping certificate verification [$HELM_REPO_INSECURE]
```

## Custom Downloader
This plugin also defines the `cm://` protocol that you may specify when adding a repo:
```
$ helm repo add chartmuseum cm://my.chart.repo.com
```

The only real difference with this vs. simply using http/https, is that the environment variables above are recognized by the plugin and used to set the `Authorization` header appropriately. As in, if you do not add your repo in this way, you are unable to use token-based auth for GET requests (downloading index.yaml, chart .tgzs, etc).

By default, `cm://` translates to `https://`. If you must use `http://`, you can set the following env var:
```
$ export HELM_REPO_USE_HTTP="true"
```
# delete local repository
helm repo remove demo
```
4 changes: 4 additions & 0 deletions cmd/helmpush/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Modifications copyright (C) 2019 Alibaba Group Holding Limited / Yuning Xie (xyn1016@gmail.com)

package main

import (
Expand Down Expand Up @@ -291,6 +293,7 @@ func (p *pushCmd) push() error {
cm.CertFile(p.certFile),
cm.KeyFile(p.keyFile),
cm.InsecureSkipVerify(p.insecureSkipVerify),
cm.AutoTokenAuth(true),
)

if err != nil {
Expand Down Expand Up @@ -365,6 +368,7 @@ func (p *pushCmd) download(fileURL string) error {
cm.CertFile(p.certFile),
cm.KeyFile(p.keyFile),
cm.InsecureSkipVerify(p.insecureSkipVerify),
cm.AutoTokenAuth(true),
)

if err != nil {
Expand Down
95 changes: 93 additions & 2 deletions pkg/chartmuseum/download.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Modifications copyright (C) 2019 Alibaba Group Holding Limited / Yuning Xie (xyn1016@gmail.com)

package chartmuseum

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
Expand All @@ -21,15 +25,102 @@ func (client *Client) DownloadFile(filePath string) (*http.Response, error) {
return nil, err
}

if client.opts.accessToken != "" {
accessToken := client.opts.accessToken

if client.opts.autoTokenAuth {
resp, err := client.Do(req)
if err != nil {
return resp, err
} else if resp.StatusCode == http.StatusUnauthorized {
token, err := client.GetAuthTokenFromResponse(resp)
if err != nil {
return nil, err
}
accessToken = token
} else {
return resp, err
}
}

if accessToken != "" {
if client.opts.authHeader != "" {
req.Header.Set(client.opts.authHeader, client.opts.accessToken)
} else {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", client.opts.accessToken))
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
}
} else if client.opts.username != "" && client.opts.password != "" {
req.SetBasicAuth(client.opts.username, client.opts.password)
}

return client.Do(req)
}

func (client *Client) GetAuthTokenFromResponse(resp *http.Response) (string, error) {
authHeader := resp.Header.Get("Www-Authenticate")
authHeader = strings.Split(authHeader, " ")[1]
tokens := strings.Split(authHeader, ",")
var realm, service, scope string
for _, token := range tokens {
if strings.HasPrefix(token, "realm") {
realm = strings.Trim(token[len("realm="):], "\"")
}
if strings.HasPrefix(token, "service") {
service = strings.Trim(token[len("service="):], "\"")
}
if strings.HasPrefix(token, "scope") {
scope = strings.Trim(token[len("scope="):], "\"")
}
}
if realm == "" {
return "", fmt.Errorf("missing realm in bearer auth challenge")
}
if service == "" {
return "", fmt.Errorf("missing service in bearer auth challenge")
}
if scope == "" {
return "", fmt.Errorf("missing scope in bearer auth challenge")
}
return client.getBearerToken(realm, service, scope)
}

func (client *Client) getBearerToken(realm, service, scope string) (string, error) {
authReq, err := http.NewRequest("POST", realm, nil)
if err != nil {
return "", err
}
getParams := authReq.URL.Query()
getParams.Add("service", service)
if scope != "" {
getParams.Add("scope", scope)
}
authReq.URL.RawQuery = getParams.Encode()
if client.opts.username != "" && client.opts.password != "" {
authReq.SetBasicAuth(client.opts.username, client.opts.password)
}
resp, err := client.Do(authReq)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return "", err
}
switch resp.StatusCode {
case http.StatusUnauthorized:
return "", fmt.Errorf("unable to retrieve auth token: 401 unauthorized")
case http.StatusOK:
break
default:
return "", fmt.Errorf("unexpected http code: %d, URL: %s", resp.StatusCode, authReq.URL)
}
tokenBlob, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
token := struct {
Token string `json:"access_token"`
}{}
if err := json.Unmarshal(tokenBlob, &token); err != nil {
return "", err
}
return token.Token, nil
}
9 changes: 9 additions & 0 deletions pkg/chartmuseum/option.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Modifications copyright (C) 2019 Alibaba Group Holding Limited / Yuning Xie (xyn1016@gmail.com)

package chartmuseum

import (
Expand All @@ -21,6 +23,7 @@ type (
certFile string
keyFile string
insecureSkipVerify bool
autoTokenAuth bool
}
)

Expand Down Expand Up @@ -100,3 +103,9 @@ func InsecureSkipVerify(insecureSkipVerify bool) Option {
opts.insecureSkipVerify = insecureSkipVerify
}
}

func AutoTokenAuth(autoTokenAuth bool) Option {
return func(opts *options) {
opts.autoTokenAuth = autoTokenAuth
}
}
Loading

0 comments on commit e46d1c2

Please sign in to comment.