diff --git a/cli/cmd/create.go b/cli/cmd/create.go index 804a7d2ac..52bcfc080 100644 --- a/cli/cmd/create.go +++ b/cli/cmd/create.go @@ -9,7 +9,6 @@ import ( "github.com/edgelesssys/constellation/cli/cloud/cloudcmd" "github.com/edgelesssys/constellation/cli/cloudprovider" "github.com/edgelesssys/constellation/cli/gcp" - "github.com/edgelesssys/constellation/internal/config" "github.com/edgelesssys/constellation/internal/constants" "github.com/edgelesssys/constellation/internal/file" "github.com/spf13/afero" @@ -68,7 +67,7 @@ func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler, return err } - config, err := config.FromFile(fileHandler, flags.configPath) + config, err := readConfig(cmd.OutOrStdout(), fileHandler, flags.configPath, provider) if err != nil { return err } diff --git a/cli/cmd/init.go b/cli/cmd/init.go index 60e3da732..4e8b64841 100644 --- a/cli/cmd/init.go +++ b/cli/cmd/init.go @@ -75,7 +75,17 @@ func initialize(ctx context.Context, cmd *cobra.Command, protCl protoClient, ser return err } - config, err := config.FromFile(fileHandler, flags.configPath) + var stat state.ConstellationState + err = fileHandler.ReadJSON(constants.StateFilename, &stat) + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("nothing to initialize: %w", err) + } else if err != nil { + return err + } + + provider := cloudprovider.FromString(stat.CloudProvider) + + config, err := readConfig(cmd.OutOrStdout(), fileHandler, flags.configPath, provider) if err != nil { return err } @@ -88,17 +98,7 @@ func initialize(ctx context.Context, cmd *cobra.Command, protCl protoClient, ser }) } - protoSSHUserKeys := ssh.ToProtoSlice(sshUsers) - - var stat state.ConstellationState - err = fileHandler.ReadJSON(constants.StateFilename, &stat) - if errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("nothing to initialize: %w", err) - } else if err != nil { - return err - } - - validators, err := cloudcmd.NewValidators(cloudprovider.FromString(stat.CloudProvider), config) + validators, err := cloudcmd.NewValidators(provider, config) if err != nil { return err } @@ -141,7 +141,7 @@ func initialize(ctx context.Context, cmd *cobra.Command, protCl protoClient, ser coordinatorPrivIPs: coordinators.PrivateIPs()[1:], autoscalingNodeGroups: autoscalingNodeGroups, cloudServiceAccountURI: serviceAccount, - sshUserKeys: protoSSHUserKeys, + sshUserKeys: ssh.ToProtoSlice(sshUsers), } result, err := activate(ctx, cmd, protCl, input, validators.V()) if err != nil { diff --git a/cli/cmd/readconfig.go b/cli/cmd/readconfig.go new file mode 100644 index 000000000..64711a2e3 --- /dev/null +++ b/cli/cmd/readconfig.go @@ -0,0 +1,43 @@ +package cmd + +import ( + "errors" + "fmt" + "io" + + "github.com/edgelesssys/constellation/cli/cloudprovider" + "github.com/edgelesssys/constellation/internal/config" + "github.com/edgelesssys/constellation/internal/file" +) + +func readConfig(out io.Writer, fileHandler file.Handler, name string, provider cloudprovider.Provider) (*config.Config, error) { + cnf, err := config.FromFile(fileHandler, name) + if err != nil { + return nil, err + } + if err := validateConfig(out, cnf, provider); err != nil { + return nil, err + } + return cnf, nil +} + +func validateConfig(out io.Writer, cnf *config.Config, provider cloudprovider.Provider) error { + msgs, err := cnf.Validate() + if err != nil { + return err + } + + if len(msgs) > 0 { + fmt.Fprintln(out, "Invalid fields in config file:") + for _, m := range msgs { + fmt.Fprintln(out, "\t"+m) + } + return errors.New("invalid configuration") + } + + if provider != cloudprovider.Unknown && !cnf.HasProvider(provider) { + return fmt.Errorf("configuration doesn't contain provider: %v", provider) + } + + return nil +} diff --git a/cli/cmd/readconfig_test.go b/cli/cmd/readconfig_test.go new file mode 100644 index 000000000..d09f5a872 --- /dev/null +++ b/cli/cmd/readconfig_test.go @@ -0,0 +1,78 @@ +package cmd + +import ( + "bytes" + "testing" + + "github.com/edgelesssys/constellation/cli/cloudprovider" + "github.com/edgelesssys/constellation/internal/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidateConfig(t *testing.T) { + testCases := map[string]struct { + cnf *config.Config + provider cloudprovider.Provider + wantOutput bool + wantErr bool + }{ + "default config is valid": { + cnf: config.Default(), + }, + "config with an error": { + cnf: func() *config.Config { + cnf := config.Default() + cnf.Version = "v0" + return cnf + }(), + wantOutput: true, + wantErr: true, + }, + "config without provider is ok if no provider required": { + cnf: func() *config.Config { + cnf := config.Default() + cnf.Provider = config.ProviderConfig{} + return cnf + }(), + }, + "config with only required provider": { + cnf: func() *config.Config { + cnf := config.Default() + az := cnf.Provider.Azure + cnf.Provider = config.ProviderConfig{} + cnf.Provider.Azure = az + return cnf + }(), + provider: cloudprovider.Azure, + }, + "config without required provider": { + cnf: func() *config.Config { + cnf := config.Default() + cnf.Provider.Azure = nil + return cnf + }(), + provider: cloudprovider.Azure, + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + out := &bytes.Buffer{} + + err := validateConfig(out, tc.cnf, tc.provider) + + if tc.wantErr { + assert.Error(err) + return + } + require.NoError(err) + + assert.Equal(tc.wantOutput, out.Len() > 0) + }) + } +} diff --git a/cli/cmd/recover.go b/cli/cmd/recover.go index 19035a94f..1105c00b4 100644 --- a/cli/cmd/recover.go +++ b/cli/cmd/recover.go @@ -11,7 +11,6 @@ import ( "github.com/edgelesssys/constellation/cli/cloudprovider" "github.com/edgelesssys/constellation/cli/proto" "github.com/edgelesssys/constellation/coordinator/util" - "github.com/edgelesssys/constellation/internal/config" "github.com/edgelesssys/constellation/internal/constants" "github.com/edgelesssys/constellation/internal/file" "github.com/edgelesssys/constellation/internal/state" @@ -51,17 +50,19 @@ func recover(ctx context.Context, cmd *cobra.Command, fileHandler file.Handler, return err } - config, err := config.FromFile(fileHandler, flags.configPath) - if err != nil { - return err - } - var stat state.ConstellationState if err := fileHandler.ReadJSON(constants.StateFilename, &stat); err != nil { return err } - validators, err := cloudcmd.NewValidators(cloudprovider.FromString(stat.CloudProvider), config) + provider := cloudprovider.FromString(stat.CloudProvider) + + config, err := readConfig(cmd.OutOrStdout(), fileHandler, flags.configPath, provider) + if err != nil { + return err + } + + validators, err := cloudcmd.NewValidators(provider, config) if err != nil { return err } diff --git a/cli/cmd/verify.go b/cli/cmd/verify.go index eda8df5b0..adba6054b 100644 --- a/cli/cmd/verify.go +++ b/cli/cmd/verify.go @@ -8,7 +8,6 @@ import ( "github.com/edgelesssys/constellation/cli/cloud/cloudcmd" "github.com/edgelesssys/constellation/cli/cloudprovider" "github.com/edgelesssys/constellation/cli/proto" - "github.com/edgelesssys/constellation/internal/config" "github.com/edgelesssys/constellation/internal/constants" "github.com/edgelesssys/constellation/internal/file" "github.com/spf13/afero" @@ -49,7 +48,7 @@ func verify(ctx context.Context, cmd *cobra.Command, provider cloudprovider.Prov return err } - config, err := config.FromFile(fileHandler, flags.configPath) + config, err := readConfig(cmd.OutOrStdout(), fileHandler, flags.configPath, provider) if err != nil { return err } diff --git a/go.mod b/go.mod index f176dbc36..1e1172d59 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,9 @@ require ( github.com/coreos/go-systemd/v22 v22.3.2 github.com/docker/docker v20.10.13+incompatible github.com/docker/go-connections v0.4.0 + github.com/go-playground/locales v0.14.0 + github.com/go-playground/universal-translator v0.18.0 + github.com/go-playground/validator/v10 v10.11.0 github.com/google/go-tpm v0.3.3 github.com/google/go-tpm-tools v0.3.5 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 @@ -105,8 +108,6 @@ require ( sigs.k8s.io/yaml v1.3.0 ) -require github.com/hashicorp/errwrap v1.1.0 // indirect - require ( cloud.google.com/go v0.100.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1 // indirect @@ -161,6 +162,7 @@ require ( github.com/google/go-cmp v0.5.7 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/icholy/replace v0.5.0 github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect @@ -169,6 +171,7 @@ require ( github.com/josharian/native v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mdlayher/genetlink v1.2.0 // indirect diff --git a/go.sum b/go.sum index 093833983..3946fef60 100644 --- a/go.sum +++ b/go.sum @@ -649,8 +649,16 @@ github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/ github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= +github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -990,8 +998,9 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -1000,6 +1009,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -1230,6 +1241,7 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1303,6 +1315,9 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= diff --git a/hack/go.mod b/hack/go.mod index f535e64e3..7dc8df4c9 100644 --- a/hack/go.mod +++ b/hack/go.mod @@ -97,6 +97,9 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.11.0 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -114,6 +117,7 @@ require ( github.com/josharian/native v1.0.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect github.com/mdlayher/genetlink v1.2.0 // indirect github.com/mdlayher/netlink v1.6.0 // indirect github.com/mdlayher/socket v0.2.1 // indirect diff --git a/hack/go.sum b/hack/go.sum index 98d95febc..baf2e6d78 100644 --- a/hack/go.sum +++ b/hack/go.sum @@ -374,8 +374,16 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= +github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -602,6 +610,7 @@ github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/icholy/replace v0.5.0 h1:Nx80zYQVlowdba+3Y6dvHDnmxaGtBrDlf2wYn9GyIXQ= github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -654,8 +663,9 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -664,6 +674,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -781,6 +793,7 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -833,6 +846,9 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -1632,6 +1648,7 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.24.0 h1:J0hann2hfxWr1hinZIDefw7Q96wmCBx6SSB8IY0MdDg= k8s.io/apimachinery v0.24.0 h1:ydFCyC/DjCvFCHK5OPMKBlxayQytB8pxy8YQInd5UyQ= +k8s.io/apiserver v0.24.0 h1:GR7kGsjOMfilRvlG3Stxv/3uz/ryvJ/aZXc5pqdsNV0= k8s.io/client-go v0.24.0 h1:lbE4aB1gTHvYFSwm6eD3OF14NhFDKCejlnsGYlSJe5U= k8s.io/cluster-bootstrap v0.24.0 h1:MTs2x3Vfcl/PWvB5bfX7gzTFRyi4ZSbNSQgGJTCb6Sw= k8s.io/component-base v0.24.0 h1:h5jieHZQoHrY/lHG+HyrSbJeyfuitheBvqvKwKHVC0g= diff --git a/internal/config/config.go b/internal/config/config.go index 5c9a0c568..dc4f2efb3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,6 +11,10 @@ import ( "github.com/edgelesssys/constellation/cli/cloudprovider" "github.com/edgelesssys/constellation/internal/constants" "github.com/edgelesssys/constellation/internal/file" + "github.com/go-playground/locales/en" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + en_translations "github.com/go-playground/validator/v10/translations/en" ) const ( @@ -21,21 +25,21 @@ const ( type Config struct { // description: | // Schema version of this configuration file. - Version string `yaml:"version"` + Version string `yaml:"version" validate:"eq=v1"` // description: | // Minimum number of nodes in autoscaling group. // worker nodes. - AutoscalingNodeGroupsMin int `yaml:"autoscalingNodeGroupsMin"` + AutoscalingNodeGroupsMin int `yaml:"autoscalingNodeGroupsMin" validate:"min=0"` // description: | // Maximum number of nodes in autoscaling group. // worker nodes. - AutoscalingNodeGroupsMax int `yaml:"autoscalingNodeGroupsMax"` + AutoscalingNodeGroupsMax int `yaml:"autoscalingNodeGroupsMax" validate:"gtefield=AutoscalingNodeGroupsMin"` // description: | // Size (in GB) of data disk used for nodes. - StateDiskSizeGB int `yaml:"stateDisksizeGB"` + StateDiskSizeGB int `yaml:"stateDisksizeGB" validate:"min=0"` // description: | // Ingress firewall rules for node network. - IngressFirewall Firewall `yaml:"ingressFirewall,omitempty"` + IngressFirewall Firewall `yaml:"ingressFirewall,omitempty" validate:"dive"` // description: | // Egress firewall rules for node network. // examples: @@ -49,46 +53,46 @@ type Config struct { // ToPort: 443, // }, // }' - EgressFirewall Firewall `yaml:"egressFirewall,omitempty"` + EgressFirewall Firewall `yaml:"egressFirewall,omitempty" validate:"dive"` // description: | // Supported cloud providers & their specific configurations. - Provider ProviderConfig `yaml:"provider"` + Provider ProviderConfig `yaml:"provider" validate:"dive"` // description: | // Create SSH users on Constellation nodes. // examples: // - value: '[]UserKey{ { Username: "Alice", PublicKey: "ssh-rsa AAAAB3NzaC...5QXHKW1rufgtJeSeJ8= alice@domain.com" } }' - SSHUsers []UserKey `yaml:"sshUsers,omitempty"` + SSHUsers []UserKey `yaml:"sshUsers,omitempty" validate:"dive"` } // UserKey describes a user that should be created with corresponding public SSH key. type UserKey struct { // description: | // Username of new SSH user. - Username string `yaml:"username"` + Username string `yaml:"username" validate:"required"` // description: | // Public key of new SSH user. - PublicKey string `yaml:"publicKey"` + PublicKey string `yaml:"publicKey" validate:"required"` } type FirewallRule struct { // description: | // Name of rule. - Name string `yaml:"name"` + Name string `yaml:"name" validate:"required"` // description: | // Description for rule. Description string `yaml:"description"` // description: | // Protocol, such as 'udp' or 'tcp'. - Protocol string `yaml:"protocol"` + Protocol string `yaml:"protocol" validate:"required"` // description: | // CIDR range for which this rule is applied. - IPRange string `yaml:"iprange"` + IPRange string `yaml:"iprange" validate:"required"` // description: | // Port of start port of a range. - FromPort int `yaml:"fromport"` + FromPort int `yaml:"fromport" validate:"min=0,max=65535"` // description: | // End port of a range, or 0 if a single port is given by FromPort. - ToPort int `yaml:"toport"` + ToPort int `yaml:"toport" validate:"omitempty,gtefield=FromPort,max=65535"` } type Firewall []FirewallRule @@ -99,51 +103,51 @@ type Firewall []FirewallRule type ProviderConfig struct { // description: | // Configuration for Azure as provider. - Azure *AzureConfig `yaml:"azureConfig,omitempty"` + Azure *AzureConfig `yaml:"azureConfig,omitempty" validate:"omitempty,dive"` // description: | // Configuration for Google Cloud as provider. - GCP *GCPConfig `yaml:"gcpConfig,omitempty"` + GCP *GCPConfig `yaml:"gcpConfig,omitempty" validate:"omitempty,dive"` // description: | // Configuration for QEMU as provider. - QEMU *QEMUConfig `yaml:"qemuConfig,omitempty"` + QEMU *QEMUConfig `yaml:"qemuConfig,omitempty" validate:"omitempty,dive"` } // AzureConfig are Azure specific configuration values used by the CLI. type AzureConfig struct { // description: | // Subscription ID of the used Azure account. See: https://docs.microsoft.com/en-us/azure/azure-portal/get-subscription-tenant-id#find-your-azure-subscription - SubscriptionID string `yaml:"subscription"` + SubscriptionID string `yaml:"subscription" validate:"uuid"` // description: | // Tenant ID of the used Azure account. See: https://docs.microsoft.com/en-us/azure/azure-portal/get-subscription-tenant-id#find-your-azure-ad-tenant - TenantID string `yaml:"tenant"` + TenantID string `yaml:"tenant" validate:"uuid"` // description: | // Azure datacenter region to be used. See: https://docs.microsoft.com/en-us/azure/availability-zones/az-overview#azure-regions-with-availability-zones - Location string `yaml:"location"` + Location string `yaml:"location" validate:"required"` // description: | // Machine image used to create Constellation nodes. - Image string `yaml:"image"` + Image string `yaml:"image" validate:"required"` // description: | // Expected confidential VM measurements. Measurements Measurements `yaml:"measurements"` // description: | // Authorize spawned VMs to access Azure API. See: https://constellation-docs.edgeless.systems/6c320851-bdd2-41d5-bf10-e27427398692/#/getting-started/install?id=azure - UserAssignedIdentity string `yaml:"userassignedIdentity"` + UserAssignedIdentity string `yaml:"userassignedIdentity" validate:"required"` } // GCPConfig are GCP specific configuration values used by the CLI. type GCPConfig struct { // description: | // GCP project. See: https://support.google.com/googleapi/answer/7014113?hl=en - Project string `yaml:"project"` + Project string `yaml:"project" validate:"required"` // description: | // GCP datacenter region. See: https://cloud.google.com/compute/docs/regions-zones#available - Region string `yaml:"region"` + Region string `yaml:"region" validate:"required"` // description: | // GCP datacenter zone. See: https://cloud.google.com/compute/docs/regions-zones#available - Zone string `yaml:"zone"` + Zone string `yaml:"zone" validate:"required"` // description: | // Machine image used to create Constellation nodes. - Image string `yaml:"image"` + Image string `yaml:"image" validate:"required"` // description: | // Roles added to service account. ServiceAccountRoles []string `yaml:"serviceAccountRoles"` @@ -226,6 +230,45 @@ func Default() *Config { } } +// Validate checks the config values and returns validation error messages. +// The function only returns an error if the validation itself fails. +func (c *Config) Validate() ([]string, error) { + trans := ut.New(en.New()).GetFallback() + validate := validator.New() + if err := en_translations.RegisterDefaultTranslations(validate, trans); err != nil { + return nil, err + } + + err := validate.Struct(c) + if err == nil { + return nil, nil + } + + var errs validator.ValidationErrors + if !errors.As(err, &errs) { + return nil, err + } + + var msgs []string + for _, e := range errs { + msgs = append(msgs, e.Translate(trans)) + } + return msgs, nil +} + +// HasProvider checks whether the config contains the provider. +func (c *Config) HasProvider(provider cloudprovider.Provider) bool { + switch provider { + case cloudprovider.Azure: + return c.Provider.Azure != nil + case cloudprovider.GCP: + return c.Provider.GCP != nil + case cloudprovider.QEMU: + return c.Provider.QEMU != nil + } + return false +} + // RemoveProviderExcept removes all provider specific configurations, i.e., // sets them to nil, except the one specified. // If an unknown provider is passed, the same configuration is returned. @@ -259,8 +302,5 @@ func FromFile(fileHandler file.Handler, name string) (*Config, error) { } return nil, fmt.Errorf("could not load config from file %s: %w", name, err) } - if emptyConf.Version != Version1 { - return nil, fmt.Errorf("config version (%s) is not supported - only version %s is supported", emptyConf.Version, Version1) - } return &emptyConf, nil } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 5f864d660..a23cff10f 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -141,6 +141,61 @@ func TestFromFileStrictErrors(t *testing.T) { } } +func TestValidate(t *testing.T) { + testCases := map[string]struct { + cnf *Config + wantMsgCount int + }{ + "default config is valid": { + cnf: Default(), + wantMsgCount: 0, + }, + "config with 1 error": { + cnf: func() *Config { + cnf := Default() + cnf.Version = "v0" + return cnf + }(), + wantMsgCount: 1, + }, + "config with 2 errors": { + cnf: func() *Config { + cnf := Default() + cnf.Version = "v0" + cnf.StateDiskSizeGB = -1 + return cnf + }(), + wantMsgCount: 2, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + msgs, err := tc.cnf.Validate() + require.NoError(err) + assert.Len(msgs, tc.wantMsgCount) + }) + } +} + +func TestHasProvider(t *testing.T) { + assert := assert.New(t) + assert.False((&Config{}).HasProvider(cloudprovider.Unknown)) + assert.False((&Config{}).HasProvider(cloudprovider.Azure)) + assert.False((&Config{}).HasProvider(cloudprovider.GCP)) + assert.False((&Config{}).HasProvider(cloudprovider.QEMU)) + assert.False(Default().HasProvider(cloudprovider.Unknown)) + assert.True(Default().HasProvider(cloudprovider.Azure)) + assert.True(Default().HasProvider(cloudprovider.GCP)) + cnfWithAzure := Config{Provider: ProviderConfig{Azure: &AzureConfig{}}} + assert.False(cnfWithAzure.HasProvider(cloudprovider.Unknown)) + assert.True(cnfWithAzure.HasProvider(cloudprovider.Azure)) + assert.False(cnfWithAzure.HasProvider(cloudprovider.GCP)) +} + func TestConfigRemoveProviderExcept(t *testing.T) { testCases := map[string]struct { removeExcept cloudprovider.Provider