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-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-10-18 07:15:54 -04:00
//go:embed all:charts/*
2022-08-12 04:20:19 -04:00
var HelmFS embed . FS
type ChartLoader struct { }
2022-10-26 04:37:10 -04:00
func ( i * ChartLoader ) Load ( csp cloudprovider . Provider , conformanceMode bool , masterSecret [ ] byte , salt [ ] byte , enforcedPCRs [ ] uint32 , enforceIDKeyDigest bool , k8sVersion versions . ValidK8sVersion ) ( [ ] byte , error ) {
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 {
return nil , err
}
2022-10-18 07:15:54 -04:00
2022-10-26 04:37:10 -04:00
conServicesRelease , err := i . loadConstellationServices ( csp , masterSecret , salt , enforcedPCRs , enforceIDKeyDigest , k8sVersion )
2022-08-12 04:20:19 -04:00
if err != nil {
return nil , err
}
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-08-12 04:20:19 -04:00
chart , err := loadChartsDir ( HelmFS , "charts/cilium" )
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-08-24 03:45:02 -04:00
case cloudprovider . GCP :
2022-08-12 04:20:19 -04:00
ciliumVals = gcpVals
2022-08-24 03:45:02 -04:00
case cloudprovider . Azure :
2022-08-12 04:20:19 -04:00
ciliumVals = azureVals
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-10-24 06:23:18 -04: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.
2022-10-25 10:29:28 -04:00
func ( i * ChartLoader ) loadConstellationServices ( csp cloudprovider . Provider ,
masterSecret [ ] byte , salt [ ] byte , enforcedPCRs [ ] uint32 ,
2022-10-26 04:37:10 -04:00
enforceIDKeyDigest bool , k8sVersion versions . ValidK8sVersion ,
2022-10-25 10:29:28 -04:00
) ( helm . Release , error ) {
2022-10-21 06:01:28 -04: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-10-24 06:23:18 -04:00
enforcedPCRsJSON , err := json . Marshal ( enforcedPCRs )
if err != nil {
return helm . Release { } , fmt . Errorf ( "marshaling enforcedPCRs: %w" , err )
}
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-21 06:01:28 -04:00
"image" : versions . KmsImage ,
"masterSecret" : base64 . StdEncoding . EncodeToString ( masterSecret ) ,
"salt" : base64 . StdEncoding . EncodeToString ( salt ) ,
2022-10-24 06:23:18 -04:00
"namespace" : constants . ConstellationNamespace ,
"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 ) ,
"image" : versions . JoinImage ,
"namespace" : constants . ConstellationNamespace ,
2022-10-21 06:01:28 -04:00
} ,
2022-10-26 04:37:10 -04:00
"ccm" : map [ string ] interface { } {
"csp" : csp ,
} ,
2022-10-21 06:01:28 -04:00
}
2022-10-26 04:37:10 -04:00
switch csp {
case cloudprovider . Azure :
{
joinServiceVals , ok := vals [ "join-service" ] . ( map [ string ] any )
if ! ok {
return helm . Release { } , errors . New ( "invalid join-service values" )
}
joinServiceVals [ "enforceIdKeyDigest" ] = enforceIDKeyDigest
ccmVals , ok := vals [ "ccm" ] . ( map [ string ] any )
if ! ok {
return helm . Release { } , errors . New ( "invalid ccm values" )
}
ccmVals [ "Azure" ] = map [ string ] any {
"image" : versions . VersionConfigs [ k8sVersion ] . CloudControllerManagerImageAzure ,
}
vals [ "tags" ] = map [ string ] any {
"Azure" : true ,
}
}
case cloudprovider . GCP :
{
ccmVals , ok := vals [ "ccm" ] . ( map [ string ] any )
if ! ok {
return helm . Release { } , errors . New ( "invalid ccm values" )
}
ccmVals [ "GCP" ] = map [ string ] any {
"image" : versions . VersionConfigs [ k8sVersion ] . CloudControllerManagerImageGCP ,
}
vals [ "tags" ] = map [ string ] any {
"GCP" : true ,
}
}
case cloudprovider . QEMU :
{
vals [ "tags" ] = map [ string ] interface { } {
"QEMU" : true ,
}
}
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 {
"image" : versions . VersionConfigs [ k8sVersion ] . CloudControllerManagerImageAWS ,
}
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.
// 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
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 )
}