Add qemu cloudprovider for activation calls

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2022-05-02 10:54:54 +02:00 committed by Daniel Weiße
parent f2305b3ce6
commit 8444d5c515
10 changed files with 237 additions and 0 deletions

View File

@ -58,6 +58,8 @@ func (c *ServiceAccountCreator) Create(ctx context.Context, stat state.Constella
} }
return serviceAccount, stat, err return serviceAccount, stat, err
case cloudprovider.QEMU:
return "unsupported://qemu", stat, nil
default: default:
return "", state.ConstellationState{}, fmt.Errorf("unsupported provider: %s", provider) return "", state.ConstellationState{}, fmt.Errorf("unsupported provider: %s", provider)
} }

View File

@ -126,6 +126,11 @@ func TestServiceAccountCreator(t *testing.T) {
config: config.Default(), config: config.Default(),
wantErr: true, wantErr: true,
}, },
"qemu": {
state: state.ConstellationState{CloudProvider: "qemu"},
wantStateMutator: func(cs *state.ConstellationState) {},
config: config.Default(),
},
"unknown cloud provider": { "unknown cloud provider": {
state: state.ConstellationState{}, state: state.ConstellationState{},
config: config.Default(), config: config.Default(),

View File

@ -11,6 +11,7 @@ import (
"github.com/edgelesssys/constellation/coordinator/atls" "github.com/edgelesssys/constellation/coordinator/atls"
"github.com/edgelesssys/constellation/coordinator/attestation/azure" "github.com/edgelesssys/constellation/coordinator/attestation/azure"
"github.com/edgelesssys/constellation/coordinator/attestation/gcp" "github.com/edgelesssys/constellation/coordinator/attestation/gcp"
"github.com/edgelesssys/constellation/coordinator/attestation/qemu"
"github.com/edgelesssys/constellation/coordinator/attestation/vtpm" "github.com/edgelesssys/constellation/coordinator/attestation/vtpm"
"github.com/edgelesssys/constellation/internal/config" "github.com/edgelesssys/constellation/internal/config"
) )
@ -78,6 +79,12 @@ func (v *Validators) setPCRs(config *config.Config) error {
return err return err
} }
v.pcrs = azurePCRs v.pcrs = azurePCRs
case cloudprovider.QEMU:
qemuPCRs := *config.Provider.QEMU.PCRs
if err := v.checkPCRs(qemuPCRs); err != nil {
return err
}
v.pcrs = qemuPCRs
} }
return nil return nil
} }
@ -98,6 +105,10 @@ func (v *Validators) updateValidators() {
v.validators = []atls.Validator{ v.validators = []atls.Validator{
azure.NewValidator(v.pcrs), azure.NewValidator(v.pcrs),
} }
case cloudprovider.QEMU:
v.validators = []atls.Validator{
qemu.NewValidator(v.pcrs),
}
} }
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/edgelesssys/constellation/coordinator/atls" "github.com/edgelesssys/constellation/coordinator/atls"
"github.com/edgelesssys/constellation/coordinator/attestation/azure" "github.com/edgelesssys/constellation/coordinator/attestation/azure"
"github.com/edgelesssys/constellation/coordinator/attestation/gcp" "github.com/edgelesssys/constellation/coordinator/attestation/gcp"
"github.com/edgelesssys/constellation/coordinator/attestation/qemu"
"github.com/edgelesssys/constellation/coordinator/attestation/vtpm" "github.com/edgelesssys/constellation/coordinator/attestation/vtpm"
"github.com/edgelesssys/constellation/internal/config" "github.com/edgelesssys/constellation/internal/config"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -40,6 +41,10 @@ func TestNewValidators(t *testing.T) {
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
pcrs: testPCRs, pcrs: testPCRs,
}, },
"qemu": {
provider: cloudprovider.QEMU,
pcrs: testPCRs,
},
"no pcrs provided": { "no pcrs provided": {
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
pcrs: map[uint32][]byte{}, pcrs: map[uint32][]byte{},
@ -68,6 +73,9 @@ func TestNewValidators(t *testing.T) {
if tc.provider == cloudprovider.Azure { if tc.provider == cloudprovider.Azure {
conf.Provider.Azure = &config.AzureConfig{PCRs: &tc.pcrs} conf.Provider.Azure = &config.AzureConfig{PCRs: &tc.pcrs}
} }
if tc.provider == cloudprovider.QEMU {
conf.Provider.QEMU = &config.QEMUConfig{PCRs: &tc.pcrs}
}
validators, err := NewValidators(tc.provider, conf) validators, err := NewValidators(tc.provider, conf)
@ -287,6 +295,13 @@ func TestValidatorsV(t *testing.T) {
azure.NewValidator(newTestPCRs()), azure.NewValidator(newTestPCRs()),
}, },
}, },
"qemu": {
provider: cloudprovider.QEMU,
pcrs: newTestPCRs(),
wantVs: []atls.Validator{
qemu.NewValidator(newTestPCRs()),
},
},
} }
for name, tc := range testCases { for name, tc := range testCases {

View File

@ -384,6 +384,8 @@ func getScalingGroupsFromConfig(stat state.ConstellationState, config *config.Co
return getGCPInstances(stat, config) return getGCPInstances(stat, config)
case len(stat.AzureCoordinators) != 0: case len(stat.AzureCoordinators) != 0:
return getAzureInstances(stat, config) return getAzureInstances(stat, config)
case len(stat.QEMUCoordinators) != 0:
return getQEMUInstances(stat, config)
default: default:
return ScalingGroup{}, ScalingGroup{}, errors.New("no instances to init") return ScalingGroup{}, ScalingGroup{}, errors.New("no instances to init")
} }
@ -488,6 +490,38 @@ func getAzureInstances(stat state.ConstellationState, config *config.Config) (co
return return
} }
func getQEMUInstances(stat state.ConstellationState, config *config.Config) (coordinators, nodes ScalingGroup, err error) {
coordinatorMap := stat.QEMUCoordinators
if len(coordinatorMap) == 0 {
return ScalingGroup{}, ScalingGroup{}, errors.New("no coordinators available, can't create Constellation without any instance")
}
var coordinatorInstances Instances
for _, node := range coordinatorMap {
coordinatorInstances = append(coordinatorInstances, Instance(node))
}
// QEMU does not support autoscaling
coordinators = ScalingGroup{
Instances: coordinatorInstances,
GroupID: "",
}
nodeMap := stat.QEMUNodes
if len(nodeMap) == 0 {
return ScalingGroup{}, ScalingGroup{}, errors.New("no nodes available, can't create Constellation with one instance")
}
var nodeInstances Instances
for _, node := range nodeMap {
nodeInstances = append(nodeInstances, Instance(node))
}
// QEMU does not support autoscaling
nodes = ScalingGroup{
Instances: nodeInstances,
GroupID: "",
}
return
}
// initCompletion handels the completion of CLI arguments. It is frequently called // initCompletion handels the completion of CLI arguments. It is frequently called
// while the user types arguments of the command to suggest completion. // while the user types arguments of the command to suggest completion.
func initCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { func initCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {

View File

@ -14,6 +14,7 @@ import (
"github.com/edgelesssys/constellation/cli/ec2" "github.com/edgelesssys/constellation/cli/ec2"
"github.com/edgelesssys/constellation/cli/file" "github.com/edgelesssys/constellation/cli/file"
"github.com/edgelesssys/constellation/cli/gcp" "github.com/edgelesssys/constellation/cli/gcp"
"github.com/edgelesssys/constellation/cli/qemu"
"github.com/edgelesssys/constellation/internal/constants" "github.com/edgelesssys/constellation/internal/constants"
"github.com/edgelesssys/constellation/internal/state" "github.com/edgelesssys/constellation/internal/state"
wgquick "github.com/nmiculinic/wg-quick-go" wgquick "github.com/nmiculinic/wg-quick-go"
@ -55,6 +56,16 @@ func TestInitialize(t *testing.T) {
}, },
AzureResourceGroup: "test", AzureResourceGroup: "test",
} }
testQemuState := state.ConstellationState{
CloudProvider: "QEMU",
QEMUNodes: qemu.Instances{
"id-0": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
"id-1": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
},
QEMUCoordinators: qemu.Instances{
"id-c": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
},
}
testActivationResps := []fakeActivationRespMessage{ testActivationResps := []fakeActivationRespMessage{
{log: "testlog1"}, {log: "testlog1"},
{log: "testlog2"}, {log: "testlog2"},
@ -97,6 +108,15 @@ func TestInitialize(t *testing.T) {
vpnHandler: &stubVPNHandler{}, vpnHandler: &stubVPNHandler{},
privKey: testKey, privKey: testKey,
}, },
"initialize some qemu instances": {
existingState: testQemuState,
client: &fakeProtoClient{
respClient: &fakeActivationRespClient{responses: testActivationResps},
},
waiter: &stubStatusWaiter{},
vpnHandler: &stubVPNHandler{},
privKey: testKey,
},
"initialize vpn": { "initialize vpn": {
existingState: testAzureState, existingState: testAzureState,
client: &fakeProtoClient{ client: &fakeProtoClient{

62
cli/qemu/instances.go Normal file
View File

@ -0,0 +1,62 @@
package qemu
// copy of ec2/instances.go
// TODO(katexochen): refactor into mulitcloud package.
import "errors"
// Instance is a gcp instance.
type Instance struct {
PublicIP string
PrivateIP string
}
// Instances is a map of gcp Instances. The ID of an instance is used as key.
type Instances map[string]Instance
// IDs returns the IDs of all instances of the Constellation.
func (i Instances) IDs() []string {
var ids []string
for id := range i {
ids = append(ids, id)
}
return ids
}
// PublicIPs returns the public IPs of all the instances of the Constellation.
func (i Instances) PublicIPs() []string {
var ips []string
for _, instance := range i {
ips = append(ips, instance.PublicIP)
}
return ips
}
// PrivateIPs returns the private IPs of all the instances of the Constellation.
func (i Instances) PrivateIPs() []string {
var ips []string
for _, instance := range i {
ips = append(ips, instance.PrivateIP)
}
return ips
}
// GetOne return anyone instance out of the instances and its ID.
func (i Instances) GetOne() (string, Instance, error) {
for id, instance := range i {
return id, instance, nil
}
return "", Instance{}, errors.New("map is empty")
}
// GetOthers returns all instances but the one with the handed ID.
func (i Instances) GetOthers(id string) Instances {
others := make(Instances)
for key, instance := range i {
if key != id {
others[key] = instance
}
}
return others
}

View File

@ -0,0 +1,71 @@
package qemu
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIDs(t *testing.T) {
assert := assert.New(t)
testState := testInstances()
wantIDs := []string{"id-9", "id-10", "id-11", "id-12"}
assert.ElementsMatch(wantIDs, testState.IDs())
}
func TestPublicIPs(t *testing.T) {
assert := assert.New(t)
testState := testInstances()
wantIPs := []string{"192.0.2.1", "192.0.2.3", "192.0.2.5", "192.0.2.7"}
assert.ElementsMatch(wantIPs, testState.PublicIPs())
}
func TestPrivateIPs(t *testing.T) {
assert := assert.New(t)
testState := testInstances()
wantIPs := []string{"192.0.2.2", "192.0.2.4", "192.0.2.6", "192.0.2.8"}
assert.ElementsMatch(wantIPs, testState.PrivateIPs())
}
func TestGetOne(t *testing.T) {
assert := assert.New(t)
testState := testInstances()
id, instance, err := testState.GetOne()
assert.NoError(err)
assert.Contains(testState, id)
assert.Equal(testState[id], instance)
}
func TestGetOthers(t *testing.T) {
assert := assert.New(t)
testCases := testInstances().IDs()
for _, id := range testCases {
others := testInstances().GetOthers(id)
assert.NotContains(others, id)
wantInstances := testInstances()
delete(wantInstances, id)
assert.ElementsMatch(others.IDs(), wantInstances.IDs())
}
}
func testInstances() Instances {
return Instances{
"id-9": {
PublicIP: "192.0.2.1",
PrivateIP: "192.0.2.2",
},
"id-10": {
PublicIP: "192.0.2.3",
PrivateIP: "192.0.2.4",
},
"id-11": {
PublicIP: "192.0.2.5",
PrivateIP: "192.0.2.6",
},
"id-12": {
PublicIP: "192.0.2.7",
PrivateIP: "192.0.2.8",
},
}
}

View File

@ -30,6 +30,11 @@ var (
uint32(vtpm.PCRIndexOwnerID): {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, uint32(vtpm.PCRIndexOwnerID): {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
uint32(vtpm.PCRIndexClusterID): {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, uint32(vtpm.PCRIndexClusterID): {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
} }
qemuPCRs = map[uint32][]byte{
uint32(vtpm.PCRIndexOwnerID): {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
uint32(vtpm.PCRIndexClusterID): {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
}
) )
// Config defines a configuration used by the CLI. // Config defines a configuration used by the CLI.
@ -193,6 +198,9 @@ func Default() *Config {
}, },
PCRs: pcrPtr(gcpPCRs), PCRs: pcrPtr(gcpPCRs),
}, },
QEMU: &QEMUConfig{
PCRs: pcrPtr(qemuPCRs),
},
}, },
} }
} }
@ -216,6 +224,7 @@ type ProviderConfig struct {
EC2 *EC2Config `json:"ec2config,omitempty"` EC2 *EC2Config `json:"ec2config,omitempty"`
Azure *AzureConfig `json:"azureconfig,omitempty"` Azure *AzureConfig `json:"azureconfig,omitempty"`
GCP *GCPConfig `json:"gcpconfig,omitempty"` GCP *GCPConfig `json:"gcpconfig,omitempty"`
QEMU *QEMUConfig `json:"qemuconfig,omitempty"`
} }
// EC2Config are AWS EC2 specific configuration values used by the CLI. // EC2Config are AWS EC2 specific configuration values used by the CLI.
@ -248,6 +257,10 @@ type GCPConfig struct {
PCRs *map[uint32][]byte `json:"pcrs,omitempty"` PCRs *map[uint32][]byte `json:"pcrs,omitempty"`
} }
type QEMUConfig struct {
PCRs *map[uint32][]byte `json:"pcrs,omitempty"`
}
func pcrPtr(pcrs map[uint32][]byte) *map[uint32][]byte { func pcrPtr(pcrs map[uint32][]byte) *map[uint32][]byte {
return &pcrs return &pcrs
} }

View File

@ -4,6 +4,7 @@ import (
"github.com/edgelesssys/constellation/cli/azure" "github.com/edgelesssys/constellation/cli/azure"
"github.com/edgelesssys/constellation/cli/ec2" "github.com/edgelesssys/constellation/cli/ec2"
"github.com/edgelesssys/constellation/cli/gcp" "github.com/edgelesssys/constellation/cli/gcp"
"github.com/edgelesssys/constellation/cli/qemu"
) )
// ConstellationState is the state of a Constellation. // ConstellationState is the state of a Constellation.
@ -40,4 +41,7 @@ type ConstellationState struct {
AzureNodesScaleSet string `json:"azurenodesscaleset,omitempty"` AzureNodesScaleSet string `json:"azurenodesscaleset,omitempty"`
AzureCoordinatorsScaleSet string `json:"azurecoordinatorsscaleset,omitempty"` AzureCoordinatorsScaleSet string `json:"azurecoordinatorsscaleset,omitempty"`
AzureADAppObjectID string `json:"azureadappobjectid,omitempty"` AzureADAppObjectID string `json:"azureadappobjectid,omitempty"`
QEMUNodes qemu.Instances `json:"qemunodes,omitempty"`
QEMUCoordinators qemu.Instances `json:"qemucoordinators,omitempty"`
} }