Add feature of encrypt and decrypt helm charts

Signed-off-by: xinjiexu <xinjiexu@tencent.com>
pull/5048/head
xinjiexu 7 years ago
parent eaa8cd761f
commit 284164c40c

@ -59,6 +59,7 @@ type packageCmd struct {
version string
appVersion string
destination string
cipherKey string
dependencyUpdate bool
out io.Writer
@ -103,6 +104,7 @@ func newPackageCmd(out io.Writer) *cobra.Command {
f.StringVar(&pkg.version, "version", "", "set the version on the chart to this semver version")
f.StringVar(&pkg.appVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&pkg.destination, "destination", "d", ".", "location to write the chart.")
f.StringVar(&pkg.cipherKey, "cipher-key", "", "the cipher key to encrypt this package")
f.BoolVarP(&pkg.dependencyUpdate, "dependency-update", "u", false, `update dependencies from "requirements.yaml" to dir "charts/" before packaging`)
return cmd
@ -173,6 +175,14 @@ func (p *packageCmd) run() error {
dest = p.destination
}
// encrypt this chart
if len(p.cipherKey) > 0 {
ch, err = chartutil.Encrypt(ch, p.cipherKey)
if err != nil {
return fmt.Errorf("Failed to encrypt: %s", err)
}
}
name, err := chartutil.Save(ch, dest)
if err == nil {
fmt.Fprintf(p.out, "Successfully packaged chart and saved it to: %s\n", name)

@ -61,6 +61,8 @@ const (
tlsCertsEnvVar = "TILLER_TLS_CERTS"
// historyMaxEnvVar is the name of the env var for setting max history.
historyMaxEnvVar = "TILLER_HISTORY_MAX"
// cipherKeyEnvVar is the name of the env var for the cipher key to decrypt chart.
cipherKeyEnvVar = "CIPHER_KEY"
storageMemory = "memory"
storageConfigMap = "configmap"
@ -84,6 +86,7 @@ var (
certFile = flag.String("tls-cert", tlsDefaultsFromEnv("tls-cert"), "path to TLS certificate file")
caCertFile = flag.String("tls-ca-cert", tlsDefaultsFromEnv("tls-ca-cert"), "trust certificates signed by this CA")
maxHistory = flag.Int("history-max", historyMaxFromEnv(), "maximum number of releases kept in release history, with 0 meaning no limit")
cipherKey = flag.String("cipher-key", cipherKeyDefaultsFromEnv(), "the cipher key to decrypt chart")
printVersion = flag.Bool("version", false, "print the version number")
// rootServer is the root gRPC server.
@ -151,6 +154,8 @@ func start() {
kubeClient.Log = newLogger("kube").Printf
env.KubeClient = kubeClient
env.CipherKey = *cipherKey
if *tlsEnable || *tlsVerify {
opts := tlsutil.Options{CertFile: *certFile, KeyFile: *keyFile}
if *tlsVerify {
@ -289,3 +294,4 @@ func historyMaxFromEnv() int {
func tlsEnableEnvVarDefault() bool { return os.Getenv(tlsEnableEnvVar) != "" }
func tlsVerifyEnvVarDefault() bool { return os.Getenv(tlsVerifyEnvVar) != "" }
func cipherKeyDefaultsFromEnv() string { return os.Getenv(cipherKeyEnvVar) }

@ -23,6 +23,7 @@ helm package [flags] [CHART_PATH] [...]
```
--app-version string set the appVersion on the chart to this version
--cipher-key string the cipher key to encrypt this package
-u, --dependency-update update dependencies from "requirements.yaml" to dir "charts/" before packaging
-d, --destination string location to write the chart. (default ".")
-h, --help help for package
@ -49,4 +50,4 @@ helm package [flags] [CHART_PATH] [...]
* [helm](helm.md) - The Helm package manager for Kubernetes.
###### Auto generated by spf13/cobra on 1-Aug-2018
###### Auto generated by spf13/cobra on 12-Dec-2018

@ -0,0 +1,65 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aesutil
import (
"bytes"
"crypto/aes"
"crypto/cipher"
)
// Padding ciphertext to multiples of blockSize
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// UnPadding origData
func PKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
// AES encrypt orig data with key, and return encrypted data
func AesEncrypt(origData, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
origData = PKCS7Padding(origData, blockSize)
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
crypted := make([]byte, len(origData))
blockMode.CryptBlocks(crypted, origData)
return crypted, nil
}
// AES decrypt crypted with key, and return orig data
func AesDecrypt(crypted, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCS7UnPadding(origData)
return origData, nil
}

@ -0,0 +1,112 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package chartutil
import (
"k8s.io/helm/pkg/aesutil"
"k8s.io/helm/pkg/proto/hapi/chart"
)
const ENCRYPTED_ANNOTATION_KEY = "encrypted"
// Encrypt Data of templates and values in Chart with cipherKey
func Encrypt(ch *chart.Chart, cipherKey string) (*chart.Chart, error) {
cipherBytes := []byte(cipherKey)
// encrypt templates and values
ch, err := encryptChart(ch, cipherBytes)
if err != nil {
return ch, err
}
// encrypt dependencies
for _, c := range ch.Dependencies {
_, err := encryptChart(c, cipherBytes)
if err != nil {
return ch, err
}
}
return ch, nil
}
func encryptChart(ch *chart.Chart, cipherBytes []byte) (*chart.Chart, error) {
shouldEncrypt := false
if len(ch.Metadata.Annotations) == 0 {
shouldEncrypt = true
} else if v, ok := ch.Metadata.Annotations[ENCRYPTED_ANNOTATION_KEY]; !ok || v != "true" {
shouldEncrypt = true
}
if shouldEncrypt {
for _, template := range ch.Templates {
data, err := aesutil.AesEncrypt(template.Data, cipherBytes)
if err != nil {
return ch, err
}
template.Data = data
}
if ch.Values != nil {
data, err := aesutil.AesEncrypt([]byte(ch.Values.Raw), cipherBytes)
if err != nil {
return ch, err
}
ch.Values.Raw = string(data)
}
if ch.Metadata.Annotations == nil {
ch.Metadata.Annotations = map[string]string{}
}
ch.Metadata.Annotations[ENCRYPTED_ANNOTATION_KEY] = "true"
}
return ch, nil
}
// Decrypt Data of templates and values in Chart with cipherKey
func Decrypt(ch *chart.Chart, cipherKey string) (*chart.Chart, error) {
cipherBytes := []byte(cipherKey)
// decrypt templates and values
ch, err := decryptChart(ch, cipherBytes)
if err != nil {
return ch, err
}
// decrypt dependencies
for _, c := range ch.Dependencies {
_, err := decryptChart(c, cipherBytes)
if err != nil {
return ch, err
}
}
return ch, nil
}
func decryptChart(ch *chart.Chart, cipherBytes []byte) (*chart.Chart, error) {
if len(ch.Metadata.Annotations) > 0 {
if v, ok := ch.Metadata.Annotations[ENCRYPTED_ANNOTATION_KEY]; ok && v == "true" {
for _, template := range ch.Templates {
data, err := aesutil.AesDecrypt(template.Data, cipherBytes)
if err != nil {
return ch, err
}
template.Data = data
}
if ch.Values != nil {
data, err := aesutil.AesDecrypt([]byte(ch.Values.Raw), cipherBytes)
if err != nil {
return ch, err
}
ch.Values.Raw = string(data)
}
ch.Metadata.Annotations[ENCRYPTED_ANNOTATION_KEY] = "false"
}
}
return ch, nil
}

@ -207,6 +207,8 @@ type Environment struct {
Releases *storage.Storage
// KubeClient is a Kubernetes API client.
KubeClient KubeClient
// the cipher key to decrypt chart
CipherKey string
}
// New returns an environment initialized with the defaults.

@ -32,6 +32,14 @@ import (
// InstallRelease installs a release and stores the release record.
func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) {
// decrypt this chart if possible
if len(s.env.CipherKey) > 0 {
ch, err := chartutil.Decrypt(req.Chart, s.env.CipherKey)
if err != nil {
return nil, fmt.Errorf("Failed to decrypt: %s", err)
}
req.Chart = ch
}
s.Log("preparing install for %s", req.Name)
rel, err := s.prepareRelease(req)
if err != nil {

@ -31,6 +31,14 @@ import (
// UpdateRelease takes an existing release and new information, and upgrades the release.
func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
// decrypt this chart if possible
if len(s.env.CipherKey) > 0 {
ch, err := chartutil.Decrypt(req.Chart, s.env.CipherKey)
if err != nil {
return nil, fmt.Errorf("Failed to decrypt: %s", err)
}
req.Chart = ch
}
if err := validateReleaseName(req.Name); err != nil {
s.Log("updateRelease: Release name is invalid: %s", req.Name)
return nil, err

Loading…
Cancel
Save