cli: new flag to set the attestation type for config generate (#1769)

* add attestation flag to specify type in config
This commit is contained in:
Adrian Stobbe 2023-05-17 16:53:56 +02:00 committed by GitHub
parent e7b7a544f0
commit f99e06b63b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 336 additions and 42 deletions

View file

@ -15,6 +15,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
"github.com/spf13/afero"
@ -36,13 +37,15 @@ func newConfigGenerateCmd() *cobra.Command {
}
cmd.Flags().StringP("file", "f", constants.ConfigFilename, "path to output file, or '-' for stdout")
cmd.Flags().StringP("kubernetes", "k", semver.MajorMinor(config.Default().KubernetesVersion), "Kubernetes version to use in format MAJOR.MINOR")
cmd.Flags().StringP("attestation", "a", "", fmt.Sprintf("attestation variant to use %s. If not specified, the default for the cloud provider is used", printFormattedSlice(variant.GetAvailableAttestationTypes())))
return cmd
}
type generateFlags struct {
file string
k8sVersion string
file string
k8sVersion string
attestationVariant variant.Variant
}
type configGenerateCmd struct {
@ -69,7 +72,10 @@ func (cg *configGenerateCmd) configGenerate(cmd *cobra.Command, fileHandler file
cg.log.Debugf("Parsed flags as %v", flags)
cg.log.Debugf("Using cloud provider %s", provider.String())
conf := createConfig(provider)
conf, err := createConfigWithAttestationType(provider, flags.attestationVariant)
if err != nil {
return fmt.Errorf("creating config: %w", err)
}
conf.KubernetesVersion = flags.k8sVersion
if flags.file == "-" {
content, err := encoder.NewEncoder(conf).Encode()
@ -96,7 +102,7 @@ func (cg *configGenerateCmd) configGenerate(cmd *cobra.Command, fileHandler file
}
// createConfig creates a config file for the given provider.
func createConfig(provider cloudprovider.Provider) *config.Config {
func createConfigWithAttestationType(provider cloudprovider.Provider, attestationVariant variant.Variant) (*config.Config, error) {
conf := config.Default()
conf.RemoveProviderExcept(provider)
@ -105,7 +111,25 @@ func createConfig(provider cloudprovider.Provider) *config.Config {
conf.StateDiskSizeGB = 10
}
return conf
if provider == cloudprovider.Unknown {
return conf, nil
}
if attestationVariant.Equal(variant.Dummy{}) {
attestationVariant = variant.GetDefaultAttestation(provider)
if attestationVariant.Equal(variant.Dummy{}) {
return nil, fmt.Errorf("provider %s does not have a default attestation variant", provider)
}
} else if !variant.ValidProvider(provider, attestationVariant) {
return nil, fmt.Errorf("provider %s does not support attestation type %s", provider, attestationVariant)
}
conf.SetAttestation(attestationVariant)
return conf, nil
}
// createConfig creates a config file for the given provider.
func createConfig(provider cloudprovider.Provider) *config.Config {
res, _ := createConfigWithAttestationType(provider, variant.Dummy{})
return res
}
// supportedVersions prints the supported version without v prefix and without patch version.
@ -135,13 +159,29 @@ func parseGenerateFlags(cmd *cobra.Command) (generateFlags, error) {
return generateFlags{}, fmt.Errorf("resolving kuberentes version from flag: %w", err)
}
attestationString, err := cmd.Flags().GetString("attestation")
if err != nil {
return generateFlags{}, fmt.Errorf("parsing attestation flag: %w", err)
}
var attestationType variant.Variant
// if no attestation type is specified, use the default for the cloud provider
if attestationString == "" {
attestationType = variant.Dummy{}
} else {
attestationType, err = variant.FromString(attestationString)
if err != nil {
return generateFlags{}, fmt.Errorf("invalid attestation variant: %s", attestationString)
}
}
return generateFlags{
file: file,
k8sVersion: resolvedVersion,
file: file,
k8sVersion: resolvedVersion,
attestationVariant: attestationType,
}, nil
}
// createCompletion handles the completion of the create command. It is frequently called
// generateCompletion handles the completion of the create command. It is frequently called
// while the user types arguments of the command to suggest completion.
func generateCompletion(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
switch len(args) {
@ -167,3 +207,15 @@ func resolveK8sVersion(k8sVersion string) (string, error) {
return extendedVersion, nil
}
func printFormattedSlice[T any](input []T) string {
return fmt.Sprintf("{%s}", strings.Join(toString(input), "|"))
}
func toString[T any](t []T) []string {
var res []string
for _, v := range t {
res = append(res, fmt.Sprintf("%v", v))
}
return res
}

View file

@ -8,6 +8,7 @@ package cmd
import (
"bytes"
"fmt"
"testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -15,8 +16,10 @@ import (
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/mod/semver"
@ -87,7 +90,7 @@ func TestConfigGenerateDefaultGCPSpecific(t *testing.T) {
cmd := newConfigGenerateCmd()
wantConf := config.Default()
wantConf.RemoveProviderExcept(cloudprovider.GCP)
wantConf.RemoveProviderAndAttestationExcept(cloudprovider.GCP)
cg := &configGenerateCmd{log: logger.NewTest(t)}
require.NoError(cg.configGenerate(cmd, fileHandler, cloudprovider.GCP))
@ -139,3 +142,133 @@ func TestConfigGenerateStdOut(t *testing.T) {
assert.Equal(*config.Default(), readConfig)
}
func TestNoValidProviderAttestationCombination(t *testing.T) {
assert := assert.New(t)
tests := []struct {
provider cloudprovider.Provider
attestation variant.Variant
}{
{cloudprovider.Azure, variant.AWSNitroTPM{}},
{cloudprovider.AWS, variant.AzureTrustedLaunch{}},
{cloudprovider.GCP, variant.AWSNitroTPM{}},
{cloudprovider.QEMU, variant.GCPSEVES{}},
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
_, err := createConfigWithAttestationType(test.provider, test.attestation)
assert.Error(err)
})
}
}
func TestValidProviderAttestationCombination(t *testing.T) {
defaultAttestation := config.Default().Attestation
tests := []struct {
provider cloudprovider.Provider
attestation variant.Variant
expected config.AttestationConfig
}{
{
cloudprovider.Azure,
variant.AzureTrustedLaunch{},
config.AttestationConfig{AzureTrustedLaunch: defaultAttestation.AzureTrustedLaunch},
},
{
cloudprovider.Azure,
variant.AzureSEVSNP{},
config.AttestationConfig{AzureSEVSNP: defaultAttestation.AzureSEVSNP},
},
{
cloudprovider.AWS,
variant.AWSNitroTPM{},
config.AttestationConfig{AWSNitroTPM: defaultAttestation.AWSNitroTPM},
},
{
cloudprovider.GCP,
variant.GCPSEVES{},
config.AttestationConfig{GCPSEVES: defaultAttestation.GCPSEVES},
},
{
cloudprovider.QEMU,
variant.QEMUVTPM{},
config.AttestationConfig{QEMUVTPM: defaultAttestation.QEMUVTPM},
},
{
cloudprovider.OpenStack,
variant.QEMUVTPM{},
config.AttestationConfig{QEMUVTPM: defaultAttestation.QEMUVTPM},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("Provider:%s,Attestation:%s", test.provider, test.attestation), func(t *testing.T) {
sut, err := createConfigWithAttestationType(test.provider, test.attestation)
assert := assert.New(t)
assert.NoError(err)
assert.Equal(test.expected, sut.Attestation)
})
}
}
func TestAttestationArgument(t *testing.T) {
defaultAttestation := config.Default().Attestation
tests := []struct {
name string
provider cloudprovider.Provider
expectErr bool
expectedCfg config.AttestationConfig
setFlag func(*cobra.Command) error
}{
{
name: "InvalidAttestationArgument",
provider: cloudprovider.Unknown,
expectErr: true,
setFlag: func(cmd *cobra.Command) error {
return cmd.Flags().Set("attestation", "unknown")
},
},
{
name: "ValidAttestationArgument",
provider: cloudprovider.Azure,
expectErr: false,
setFlag: func(cmd *cobra.Command) error {
return cmd.Flags().Set("attestation", "azure-trustedlaunch")
},
expectedCfg: config.AttestationConfig{AzureTrustedLaunch: defaultAttestation.AzureTrustedLaunch},
},
{
name: "WithoutAttestationArgument",
provider: cloudprovider.Azure,
expectErr: false,
setFlag: func(cmd *cobra.Command) error {
return nil
},
expectedCfg: config.AttestationConfig{AzureSEVSNP: defaultAttestation.AzureSEVSNP},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := assert.New(t)
assert := assert.New(t)
cmd := newConfigGenerateCmd()
require.NoError(test.setFlag(cmd))
fileHandler := file.NewHandler(afero.NewMemMapFs())
cg := &configGenerateCmd{log: logger.NewTest(t)}
err := cg.configGenerate(cmd, fileHandler, test.provider)
if test.expectErr {
assert.Error(err)
} else {
assert.NoError(err)
var readConfig config.Config
require.NoError(fileHandler.ReadYAML(constants.ConfigFilename, &readConfig))
assert.Equal(test.expectedCfg, readConfig.Attestation)
}
})
}
}

View file

@ -429,7 +429,7 @@ func TestAttestation(t *testing.T) {
cfg := config.Default()
cfg.Image = "image"
cfg.RemoveProviderExcept(cloudprovider.QEMU)
cfg.RemoveProviderAndAttestationExcept(cloudprovider.QEMU)
cfg.Attestation.QEMUVTPM.Measurements[0] = measurements.WithAllBytes(0x00, measurements.Enforce, measurements.PCRMeasurementLength)
cfg.Attestation.QEMUVTPM.Measurements[1] = measurements.WithAllBytes(0x11, measurements.Enforce, measurements.PCRMeasurementLength)
cfg.Attestation.QEMUVTPM.Measurements[2] = measurements.WithAllBytes(0x22, measurements.Enforce, measurements.PCRMeasurementLength)
@ -554,7 +554,7 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
conf.Attestation.QEMUVTPM.Measurements[12] = measurements.WithAllBytes(0xcc, measurements.Enforce, measurements.PCRMeasurementLength)
}
conf.RemoveProviderExcept(csp)
conf.RemoveProviderAndAttestationExcept(csp)
return conf
}

View file

@ -216,7 +216,7 @@ func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler,
config := config.Default()
config.Name = constants.MiniConstellationUID
config.RemoveProviderExcept(cloudprovider.QEMU)
config.RemoveProviderAndAttestationExcept(cloudprovider.QEMU)
config.StateDiskSizeGB = 8
// only release images (e.g. v2.7.0) use the production NVRAM