Refactor Helm deployments (#341)

* Wrap KMS deployment in one main chart that
deploys all other services. Other services will follow.
* Use .tgz via helm-package as serialization format
* Change Release type to carry chart as byte slice
* Remove KMSConfig
* Use json-schema to validate values
* Extend release.md to mention updating helm charts
This commit is contained in:
Otto Bittner 2022-10-21 12:01:28 +02:00 committed by GitHub
parent 10a207c7ec
commit 07f02a442c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 261 additions and 119 deletions

View file

@ -0,0 +1,9 @@
apiVersion: v2
name: constellation-services
description: A chart to deploy all microservices that are part of a valid constellation cluster
type: application
version: 2.2.0-pre
dependencies:
- name: kms
version: 2.2.0-pre

View file

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View file

@ -2,5 +2,4 @@ apiVersion: v2
name: kms
description: A Helm chart to deploy the Constellation Key Management Service
type: application
version: 0.1.0
appVersion: "2.1.0"
version: 2.2.0-pre

View file

@ -18,8 +18,8 @@ spec:
spec:
containers:
- args:
- --port={{ .Values.kmsPort }}
image: {{ .Values.kmsImage }}
- --port={{ .Values.port }}
image: {{ .Values.image }}
name: kms
resources: {}
volumeMounts:

View file

@ -6,9 +6,9 @@ metadata:
spec:
ports:
- name: grpc
port: {{ .Values.kmsPort }}
port: {{ .Values.port }}
protocol: TCP
targetPort: {{ .Values.kmsPort }}
targetPort: {{ .Values.port }}
selector:
k8s-app: kms
type: ClusterIP

View file

@ -0,0 +1,30 @@
{
"$schema": "https://json-schema.org/draft-07/schema#",
"properties": {
"image": {
"description": "Container image to use for the spawned pods.",
"type": "string",
"examples": ["ghcr.io/edgelesssys/constellation/kms:latest"],
"pattern": "ghcr.io/edgelesssys/constellation/kms:*"
},
"masterSecret": {
"description": "Secret used to derive key material within the cluster",
"type": "string",
"examples": ["h1ydxM+1LKhL6kfj3XJnCYvTPnQGUgU0stk91ebEVqM="],
"minLength": 44
},
"salt": {
"description": "Salt for key derivation within the cluster",
"type": "string",
"examples": ["loC4hhWwFH5rHAKq5/EshSWk1jwkrf22VuHc2SGsWdc="],
"minLength": 44
}
},
"required": [
"image",
"salt",
"masterSecret"
],
"title": "Values",
"type": "object"
}

View file

@ -1,19 +1,13 @@
# Namespace to which KMS will be deployed.
namespace: "kube-system"
# Port on which the service will listen.
kmsPort: 9000
port: 9000
# Name of the ConfigMap that holds measurements and other info.
joinConfigCMName: join-config
# Path to which secrets/CMs are mounted.
serviceBasePath: /var/config
# Container image.
kmsImage: setFullImagePathHere
# Salt for key derivation.
salt: ""
# Name of the key within the respective secret that holds the salt.
saltKeyName: salt
# MasterSecret for the cluster.
masterSecret: ""
# Name of the secret that contains the master secret.
masterSecretName: constellation-mastersecret
# Name of the key within the respective secret that holds the master secret.

View file

@ -9,9 +9,11 @@ package helm
import (
"bytes"
"embed"
"encoding/base64"
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
@ -23,6 +25,7 @@ import (
"helm.sh/helm/pkg/ignore"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
)
// Run `go generate` to deterministically create the patched Helm deployment for cilium
@ -33,17 +36,18 @@ var HelmFS embed.FS
type ChartLoader struct{}
func (i *ChartLoader) Load(csp cloudprovider.Provider, conformanceMode bool) ([]byte, error) {
func (i *ChartLoader) Load(csp cloudprovider.Provider, conformanceMode bool, masterSecret []byte, salt []byte) ([]byte, error) {
ciliumRelease, err := i.loadCilium(csp, conformanceMode)
if err != nil {
return nil, err
}
kmsRelease, err := i.loadKMS()
conServicesRelease, err := i.loadConstellationServices(masterSecret, salt)
if err != nil {
return nil, err
}
releases := helm.Releases{Cilium: ciliumRelease, KMS: kmsRelease}
releases := helm.Releases{Cilium: ciliumRelease, ConstellationServices: conServicesRelease}
rel, err := json.Marshal(releases)
if err != nil {
return nil, err
@ -54,8 +58,14 @@ func (i *ChartLoader) Load(csp cloudprovider.Provider, conformanceMode bool) ([]
func (i *ChartLoader) loadCilium(csp cloudprovider.Provider, conformanceMode bool) (helm.Release, error) {
chart, err := loadChartsDir(HelmFS, "charts/cilium")
if err != nil {
return helm.Release{}, err
return helm.Release{}, fmt.Errorf("loading cilium chart: %w", err)
}
chartRaw, err := i.marshalChart(chart)
if err != nil {
return helm.Release{}, fmt.Errorf("packaging chart: %w", err)
}
var ciliumVals map[string]interface{}
switch csp {
case cloudprovider.GCP:
@ -77,27 +87,54 @@ func (i *ChartLoader) loadCilium(csp cloudprovider.Provider, conformanceMode boo
}
return helm.Release{Chart: chart, Values: ciliumVals, ReleaseName: "cilium", Wait: true}, nil
return helm.Release{Chart: chartRaw, Values: ciliumVals, ReleaseName: "cilium", Wait: true}, nil
}
func (i *ChartLoader) loadKMS() (helm.Release, error) {
chart, err := loadChartsDir(HelmFS, "charts/edgeless/kms")
func (i *ChartLoader) loadConstellationServices(masterSecret []byte, salt []byte) (helm.Release, error) {
chart, err := loadChartsDir(HelmFS, "charts/edgeless/constellation-services")
if err != nil {
return helm.Release{}, err
}
kmsVals := map[string]interface{}{
"namespace": constants.ConstellationNamespace,
"kmsPort": constants.KMSPort,
"joinConfigCMName": constants.JoinConfigMap,
"serviceBasePath": constants.ServiceBasePath,
"kmsImage": versions.KmsImage,
"masterSecretName": constants.ConstellationMasterSecretStoreName,
"masterSecretKeyName": constants.ConstellationMasterSecretKey,
"saltKeyName": constants.ConstellationSaltKey,
"measurementsFilename": constants.MeasurementsFilename,
return helm.Release{}, fmt.Errorf("loading constellation-services chart: %w", err)
}
return helm.Release{Chart: chart, Values: kmsVals, ReleaseName: "kms", Wait: true}, nil
chartRaw, err := i.marshalChart(chart)
if err != nil {
return helm.Release{}, fmt.Errorf("packaging chart: %w", err)
}
vals := map[string]interface{}{
"kms": map[string]interface{}{
"namespace": constants.ConstellationNamespace,
"port": constants.KMSPort,
"joinConfigCMName": constants.JoinConfigMap,
"serviceBasePath": constants.ServiceBasePath,
"image": versions.KmsImage,
"masterSecretName": constants.ConstellationMasterSecretStoreName,
"masterSecretKeyName": constants.ConstellationMasterSecretKey,
"saltKeyName": constants.ConstellationSaltKey,
"measurementsFilename": constants.MeasurementsFilename,
"masterSecret": base64.StdEncoding.EncodeToString(masterSecret),
"salt": base64.StdEncoding.EncodeToString(salt),
},
}
return helm.Release{Chart: chartRaw, Values: vals, ReleaseName: "constellation-services", Wait: true}, nil
}
// marshalChart takes a Chart object, packages it to a temporary file and returns the content of that file.
// We currently need to take this approach of marshaling as dependencies are not marshaled correctly with json.Marshal.
// This stems from the fact that chart.Chart does not export the dependencies property.
// See: https://github.com/helm/helm/issues/11454
func (i *ChartLoader) marshalChart(chart *chart.Chart) ([]byte, error) {
path, err := chartutil.Save(chart, os.TempDir())
defer os.Remove(path)
if err != nil {
return nil, fmt.Errorf("packaging chart: %w", err)
}
chartRaw, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading packaged chart: %w", err)
}
return chartRaw, nil
}
// taken from loader.LoadDir from the helm go module

View file

@ -0,0 +1,34 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package helm
import (
"bytes"
"encoding/json"
"testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/deploy/helm"
"github.com/stretchr/testify/assert"
"helm.sh/helm/v3/pkg/chart/loader"
)
func TestLoad(t *testing.T) {
assert := assert.New(t)
chartLoader := ChartLoader{}
release, err := chartLoader.Load(cloudprovider.GCP, true, []byte("secret"), []byte("salt"))
assert.NoError(err)
var helmReleases helm.Releases
err = json.Unmarshal(release, &helmReleases)
assert.NoError(err)
reader := bytes.NewReader(helmReleases.ConstellationServices.Chart)
chart, err := loader.LoadArchive(reader)
assert.NoError(err)
assert.NotNil(chart.Dependencies())
}