2022-09-05 03:06:08 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2022-08-12 04:20:19 -04:00
package helm
import (
"bytes"
"embed"
2022-10-21 06:01:28 -04:00
"encoding/base64"
2022-08-12 04:20:19 -04:00
"encoding/json"
"fmt"
"io/fs"
2022-10-21 06:01:28 -04:00
"os"
2022-08-12 04:20:19 -04:00
"path/filepath"
"strings"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
2022-11-18 04:05:02 -05:00
"github.com/edgelesssys/constellation/v2/internal/config"
2022-10-18 07:15:54 -04:00
"github.com/edgelesssys/constellation/v2/internal/constants"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/deploy/helm"
2022-10-18 07:15:54 -04:00
"github.com/edgelesssys/constellation/v2/internal/versions"
2022-08-12 04:20:19 -04:00
"github.com/pkg/errors"
"helm.sh/helm/pkg/ignore"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
2022-10-21 06:01:28 -04:00
"helm.sh/helm/v3/pkg/chartutil"
2022-08-12 04:20:19 -04:00
)
// Run `go generate` to deterministically create the patched Helm deployment for cilium
//go:generate ./generateCilium.sh
2022-11-18 04:05:02 -05:00
// Run `go generate` to load CSI driver charts from the CSI repositories
//go:generate ./update-csi-charts.sh
2022-08-12 04:20:19 -04:00
2022-10-18 07:15:54 -04:00
//go:embed all:charts/*
2022-11-09 09:57:54 -05:00
var helmFS embed . FS
2022-08-12 04:20:19 -04:00
2022-11-09 09:57:54 -05:00
// ChartLoader loads embedded helm charts.
2022-10-31 14:25:02 -04:00
type ChartLoader struct {
joinServiceImage string
kmsImage string
ccmImage string
2022-11-02 12:47:10 -04:00
cnmImage string
2022-11-03 11:42:19 -04:00
autoscalerImage string
2022-10-31 14:25:02 -04:00
}
2022-11-09 09:57:54 -05:00
// New creates a new ChartLoader.
2022-10-31 14:25:02 -04:00
func New ( csp cloudprovider . Provider , k8sVersion versions . ValidK8sVersion ) * ChartLoader {
2022-11-02 12:47:10 -04:00
var ccmImage , cnmImage string
2022-10-31 14:25:02 -04:00
switch csp {
case cloudprovider . AWS :
ccmImage = versions . VersionConfigs [ k8sVersion ] . CloudControllerManagerImageAWS
case cloudprovider . Azure :
ccmImage = versions . VersionConfigs [ k8sVersion ] . CloudControllerManagerImageAzure
2022-11-02 12:47:10 -04:00
cnmImage = versions . VersionConfigs [ k8sVersion ] . CloudNodeManagerImageAzure
2022-10-31 14:25:02 -04:00
case cloudprovider . GCP :
ccmImage = versions . VersionConfigs [ k8sVersion ] . CloudControllerManagerImageGCP
}
2022-08-12 04:20:19 -04:00
2022-10-31 14:25:02 -04:00
return & ChartLoader {
joinServiceImage : versions . JoinImage ,
kmsImage : versions . KmsImage ,
ccmImage : ccmImage ,
2022-11-02 12:47:10 -04:00
cnmImage : cnmImage ,
2022-11-03 11:42:19 -04:00
autoscalerImage : versions . VersionConfigs [ k8sVersion ] . ClusterAutoscalerImage ,
2022-10-31 14:25:02 -04:00
}
}
2022-11-09 09:57:54 -05:00
// Load the embedded helm charts.
2022-11-18 04:05:02 -05:00
func ( i * ChartLoader ) Load ( config * config . Config , conformanceMode bool , masterSecret , salt [ ] byte ) ( [ ] byte , error ) {
csp := config . GetProvider ( )
2022-10-18 07:15:54 -04:00
ciliumRelease , err := i . loadCilium ( csp , conformanceMode )
2022-08-12 04:20:19 -04:00
if err != nil {
2022-10-21 11:09:43 -04:00
return nil , fmt . Errorf ( "loading cilium: %w" , err )
2022-08-12 04:20:19 -04:00
}
2022-10-18 07:15:54 -04:00
2022-11-18 04:05:02 -05:00
conServicesRelease , err := i . loadConstellationServices ( config , masterSecret , salt )
2022-08-12 04:20:19 -04:00
if err != nil {
2022-10-21 11:09:43 -04:00
return nil , fmt . Errorf ( "loading constellation-services: %w" , err )
2022-08-12 04:20:19 -04:00
}
2022-10-21 06:01:28 -04:00
releases := helm . Releases { Cilium : ciliumRelease , ConstellationServices : conServicesRelease }
2022-10-18 07:15:54 -04:00
rel , err := json . Marshal ( releases )
if err != nil {
return nil , err
}
return rel , nil
2022-08-12 04:20:19 -04:00
}
2022-10-18 07:15:54 -04:00
func ( i * ChartLoader ) loadCilium ( csp cloudprovider . Provider , conformanceMode bool ) ( helm . Release , error ) {
2022-11-09 09:57:54 -05:00
chart , err := loadChartsDir ( helmFS , "charts/cilium" )
2022-08-12 04:20:19 -04:00
if err != nil {
2022-10-21 06:01:28 -04:00
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 )
2022-08-12 04:20:19 -04:00
}
2022-10-21 06:01:28 -04:00
2022-10-25 09:51:23 -04:00
var ciliumVals map [ string ] any
2022-10-07 03:38:43 -04:00
switch csp {
2022-10-21 08:41:31 -04:00
case cloudprovider . AWS :
ciliumVals = awsVals
2022-08-24 03:45:02 -04:00
case cloudprovider . Azure :
2022-08-12 04:20:19 -04:00
ciliumVals = azureVals
2022-10-21 08:41:31 -04:00
case cloudprovider . GCP :
ciliumVals = gcpVals
2022-08-24 03:45:02 -04:00
case cloudprovider . QEMU :
2022-08-12 04:20:19 -04:00
ciliumVals = qemuVals
default :
2022-10-18 07:15:54 -04:00
return helm . Release { } , fmt . Errorf ( "unknown csp: %s" , csp )
2022-08-12 04:20:19 -04:00
}
2022-09-20 04:07:55 -04:00
if conformanceMode {
ciliumVals [ "kubeProxyReplacementHealthzBindAddr" ] = ""
ciliumVals [ "kubeProxyReplacement" ] = "partial"
ciliumVals [ "sessionAffinity" ] = true
2022-10-25 09:51:23 -04:00
ciliumVals [ "cni" ] = map [ string ] any {
2022-09-20 04:07:55 -04:00
"chainingMode" : "portmap" ,
}
}
2022-10-21 06:01:28 -04:00
return helm . Release { Chart : chartRaw , Values : ciliumVals , ReleaseName : "cilium" , Wait : true } , nil
2022-10-18 07:15:54 -04:00
}
2022-11-18 04:05:02 -05:00
// loadConstellationServices loads the constellation-services chart from the embed.FS,
// marshals it into a helm-package .tgz and sets the values that can be set in the CLI.
func ( i * ChartLoader ) loadConstellationServices ( config * config . Config , masterSecret , salt [ ] byte ) ( helm . Release , error ) {
2022-11-09 09:57:54 -05:00
chart , err := loadChartsDir ( helmFS , "charts/edgeless/constellation-services" )
2022-10-18 07:15:54 -04:00
if err != nil {
2022-10-21 06:01:28 -04:00
return helm . Release { } , fmt . Errorf ( "loading constellation-services chart: %w" , err )
2022-10-18 07:15:54 -04:00
}
2022-10-21 06:01:28 -04:00
chartRaw , err := i . marshalChart ( chart )
if err != nil {
return helm . Release { } , fmt . Errorf ( "packaging chart: %w" , err )
2022-10-18 07:15:54 -04:00
}
2022-11-18 04:05:02 -05:00
enforcedPCRsJSON , err := json . Marshal ( config . GetEnforcedPCRs ( ) )
2022-10-24 06:23:18 -04:00
if err != nil {
return helm . Release { } , fmt . Errorf ( "marshaling enforcedPCRs: %w" , err )
}
2022-11-18 04:05:02 -05:00
csp := config . GetProvider ( )
2022-10-25 09:51:23 -04:00
vals := map [ string ] any {
"global" : map [ string ] any {
2022-10-24 06:23:18 -04:00
"kmsPort" : constants . KMSPort ,
"serviceBasePath" : constants . ServiceBasePath ,
"joinConfigCMName" : constants . JoinConfigMap ,
"k8sVersionCMName" : constants . K8sVersion ,
"internalCMName" : constants . InternalConfigMap ,
} ,
2022-10-25 09:51:23 -04:00
"kms" : map [ string ] any {
2022-10-31 14:25:02 -04:00
"image" : i . kmsImage ,
2022-10-21 06:01:28 -04:00
"masterSecret" : base64 . StdEncoding . EncodeToString ( masterSecret ) ,
"salt" : base64 . StdEncoding . EncodeToString ( salt ) ,
2022-10-24 06:23:18 -04:00
"saltKeyName" : constants . ConstellationSaltKey ,
"masterSecretKeyName" : constants . ConstellationMasterSecretKey ,
"masterSecretName" : constants . ConstellationMasterSecretStoreName ,
"measurementsFilename" : constants . MeasurementsFilename ,
} ,
2022-10-25 09:51:23 -04:00
"join-service" : map [ string ] any {
2022-10-24 06:23:18 -04:00
"csp" : csp ,
"enforcedPCRs" : string ( enforcedPCRsJSON ) ,
2022-10-31 14:25:02 -04:00
"image" : i . joinServiceImage ,
2022-10-21 06:01:28 -04:00
} ,
2022-11-02 12:47:10 -04:00
"ccm" : map [ string ] any {
2022-10-26 04:37:10 -04:00
"csp" : csp ,
} ,
2022-11-03 11:42:19 -04:00
"autoscaler" : map [ string ] any {
"csp" : csp ,
"image" : i . autoscalerImage ,
} ,
2022-10-21 06:01:28 -04:00
}
2022-10-26 04:37:10 -04:00
switch csp {
case cloudprovider . Azure :
2022-11-18 04:05:02 -05:00
joinServiceVals , ok := vals [ "join-service" ] . ( map [ string ] any )
if ! ok {
return helm . Release { } , errors . New ( "invalid join-service values" )
}
joinServiceVals [ "enforceIdKeyDigest" ] = config . EnforcesIDKeyDigest ( )
ccmVals , ok := vals [ "ccm" ] . ( map [ string ] any )
if ! ok {
return helm . Release { } , errors . New ( "invalid ccm values" )
}
ccmVals [ "Azure" ] = map [ string ] any {
"image" : i . ccmImage ,
}
vals [ "cnm" ] = map [ string ] any {
"image" : i . cnmImage ,
}
vals [ "azure" ] = map [ string ] any {
"deployCSIDriver" : config . DeployCSIDriver ( ) ,
}
2022-11-18 09:47:01 -05:00
vals [ "azuredisk-csi-driver" ] = map [ string ] any {
"node" : map [ string ] any {
"kmsPort" : constants . KMSPort ,
"kmsNamespace" : "" , // empty namespace means we use the release namespace
} ,
}
2022-11-18 04:05:02 -05:00
vals [ "tags" ] = map [ string ] any {
"Azure" : true ,
2022-10-26 04:37:10 -04:00
}
2022-11-18 04:05:02 -05:00
2022-10-26 04:37:10 -04:00
case cloudprovider . GCP :
2022-11-18 04:05:02 -05:00
ccmVals , ok := vals [ "ccm" ] . ( map [ string ] any )
if ! ok {
return helm . Release { } , errors . New ( "invalid ccm values" )
}
ccmVals [ "GCP" ] = map [ string ] any {
"image" : i . ccmImage ,
}
vals [ "gcp" ] = map [ string ] any {
"deployCSIDriver" : config . DeployCSIDriver ( ) ,
2022-10-26 04:37:10 -04:00
}
2022-11-18 04:05:02 -05:00
vals [ "gcp-compute-persistent-disk-csi-driver" ] = map [ string ] any {
"csiNode" : map [ string ] any {
"kmsPort" : constants . KMSPort ,
"kmsNamespace" : "" , // empty namespace means we use the release namespace
} ,
}
vals [ "tags" ] = map [ string ] any {
"GCP" : true ,
}
2022-10-26 04:37:10 -04:00
case cloudprovider . QEMU :
2022-11-18 04:05:02 -05:00
vals [ "tags" ] = map [ string ] interface { } {
"QEMU" : true ,
2022-10-26 04:37:10 -04:00
}
2022-11-18 04:05:02 -05:00
2022-10-26 04:37:10 -04:00
case cloudprovider . AWS :
ccmVals , ok := vals [ "ccm" ] . ( map [ string ] any )
2022-10-25 10:29:28 -04:00
if ! ok {
2022-10-26 04:37:10 -04:00
return helm . Release { } , errors . New ( "invalid ccm values" )
}
ccmVals [ "AWS" ] = map [ string ] any {
2022-10-31 14:25:02 -04:00
"image" : i . ccmImage ,
2022-10-26 04:37:10 -04:00
}
vals [ "tags" ] = map [ string ] any {
"AWS" : true ,
2022-10-25 10:29:28 -04:00
}
2022-10-24 06:23:18 -04:00
}
2022-10-21 06:01:28 -04:00
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.
func ( i * ChartLoader ) marshalChart ( chart * chart . Chart ) ( [ ] byte , error ) {
2022-10-31 14:25:02 -04:00
// A separate tmpdir path is necessary since during unit testing multiple go routines are accessing the same path, possibly deleting files for other routines.
tmpDirPath , err := os . MkdirTemp ( "" , "*" )
defer os . Remove ( tmpDirPath )
if err != nil {
return nil , fmt . Errorf ( "creating tmp dir: %w" , err )
}
path , err := chartutil . Save ( chart , tmpDirPath )
2022-10-21 06:01:28 -04:00
defer os . Remove ( path )
if err != nil {
2022-10-31 14:25:02 -04:00
return nil , fmt . Errorf ( "chartutil save: %w" , err )
2022-10-21 06:01:28 -04:00
}
chartRaw , err := os . ReadFile ( path )
if err != nil {
return nil , fmt . Errorf ( "reading packaged chart: %w" , err )
}
return chartRaw , nil
2022-08-12 04:20:19 -04:00
}
// taken from loader.LoadDir from the helm go module
// loadChartsDir loads from a directory.
//
// This loads charts only from directories.
func loadChartsDir ( efs embed . FS , dir string ) ( * chart . Chart , error ) {
utf8bom := [ ] byte { 0xEF , 0xBB , 0xBF }
// Just used for errors.
c := & chart . Chart { }
rules := ignore . Empty ( )
ifile , err := efs . ReadFile ( filepath . Join ( dir , ignore . HelmIgnore ) )
if err == nil {
r , err := ignore . Parse ( bytes . NewReader ( ifile ) )
if err != nil {
return c , err
}
rules = r
}
rules . AddDefaults ( )
files := [ ] * loader . BufferedFile { }
walk := func ( path string , d fs . DirEntry , err error ) error {
n := strings . TrimPrefix ( path , dir )
if n == "" {
// No need to process top level. Avoid bug with helmignore .* matching
// empty names. See issue https://github.com/kubernetes/helm/issues/1776.
return nil
}
// Normalize to / since it will also work on Windows
n = filepath . ToSlash ( n )
// Check input err
if err != nil {
return err
}
fi , err := d . Info ( )
if err != nil {
return err
}
if d . IsDir ( ) {
// Directory-based ignore rules should involve skipping the entire
// contents of that directory.
if rules . Ignore ( n , fi ) {
return filepath . SkipDir
}
return nil
}
// If a .helmignore file matches, skip this file.
if rules . Ignore ( n , fi ) {
return nil
}
// Irregular files include devices, sockets, and other uses of files that
// are not regular files. In Go they have a file mode type bit set.
// See https://golang.org/pkg/os/#FileMode for examples.
if ! fi . Mode ( ) . IsRegular ( ) {
return fmt . Errorf ( "cannot load irregular file %s as it has file mode type bits set" , path )
}
data , err := efs . ReadFile ( path )
if err != nil {
return errors . Wrapf ( err , "error reading %s" , n )
}
data = bytes . TrimPrefix ( data , utf8bom )
n = strings . TrimPrefix ( n , "/" )
files = append ( files , & loader . BufferedFile { Name : n , Data : data } )
return nil
}
if err := fs . WalkDir ( efs , dir , walk ) ; err != nil {
return c , err
}
return loader . LoadFiles ( files )
}