-
Notifications
You must be signed in to change notification settings - Fork 47
/
migration.go
307 lines (280 loc) · 13.7 KB
/
migration.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// Package migration contains outdated configuration formats and their migration functions.
package migration
import (
"errors"
"fmt"
"os"
"strings"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/file"
)
const (
// Version2 is the second version number for Constellation config file.
Version2 = "v2"
)
// Config defines configuration used by CLI.
type Config struct {
Version string `yaml:"version" validate:"eq=v2"`
Image string `yaml:"image" validate:"required,version_compatibility"`
Name string `yaml:"name" validate:"valid_name,required"`
StateDiskSizeGB int `yaml:"stateDiskSizeGB" validate:"min=0"`
KubernetesVersion string `yaml:"kubernetesVersion" validate:"required,supported_k8s_version"`
MicroserviceVersion string `yaml:"microserviceVersion" validate:"required,version_compatibility"`
DebugCluster *bool `yaml:"debugCluster" validate:"required"`
AttestationVariant string `yaml:"attestationVariant,omitempty" validate:"valid_attestation_variant"`
Provider ProviderConfig `yaml:"provider" validate:"dive"`
}
// ProviderConfig are cloud-provider specific configuration values used by the CLI.
// Fields should remain pointer-types so custom specific configs can nil them
// if not required.
type ProviderConfig struct {
AWS *AWSConfig `yaml:"aws,omitempty" validate:"omitempty,dive"`
Azure *AzureConfig `yaml:"azure,omitempty" validate:"omitempty,dive"`
GCP *GCPConfig `yaml:"gcp,omitempty" validate:"omitempty,dive"`
OpenStack *OpenStackConfig `yaml:"openstack,omitempty" validate:"omitempty,dive"`
QEMU *QEMUConfig `yaml:"qemu,omitempty" validate:"omitempty,dive"`
}
// AWSConfig are AWS specific configuration values used by the CLI.
type AWSConfig struct {
Region string `yaml:"region" validate:"required"`
Zone string `yaml:"zone" validate:"required"`
InstanceType string `yaml:"instanceType" validate:"lowercase,aws_instance_type"`
StateDiskType string `yaml:"stateDiskType" validate:"oneof=standard gp2 gp3 st1 sc1 io1"`
IAMProfileControlPlane string `yaml:"iamProfileControlPlane" validate:"required"`
IAMProfileWorkerNodes string `yaml:"iamProfileWorkerNodes" validate:"required"`
Measurements measurements.M `yaml:"measurements" validate:"required,no_placeholders"`
}
// AzureConfig are Azure specific configuration values used by the CLI.
type AzureConfig struct {
SubscriptionID string `yaml:"subscription" validate:"uuid"`
TenantID string `yaml:"tenant" validate:"uuid"`
Location string `yaml:"location" validate:"required"`
ResourceGroup string `yaml:"resourceGroup" validate:"required"`
UserAssignedIdentity string `yaml:"userAssignedIdentity" validate:"required"`
InstanceType string `yaml:"instanceType" validate:"azure_instance_type"`
StateDiskType string `yaml:"stateDiskType" validate:"oneof=Premium_LRS Premium_ZRS Standard_LRS StandardSSD_LRS StandardSSD_ZRS"`
DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"`
ConfidentialVM *bool `yaml:"confidentialVM,omitempty" validate:"omitempty,deprecated"`
SecureBoot *bool `yaml:"secureBoot" validate:"required"`
IDKeyDigest idkeydigest.List `yaml:"idKeyDigest" validate:"required_if=EnforceIdKeyDigest true,omitempty"`
EnforceIDKeyDigest IDKeyDigestEnforcement `yaml:"enforceIdKeyDigest" validate:"required"`
Measurements measurements.M `yaml:"measurements" validate:"required,no_placeholders"`
}
// GCPConfig are GCP specific configuration values used by the CLI.
type GCPConfig struct {
Project string `yaml:"project" validate:"required"`
Region string `yaml:"region" validate:"required"`
Zone string `yaml:"zone" validate:"required"`
ServiceAccountKeyPath string `yaml:"serviceAccountKeyPath" validate:"required"`
InstanceType string `yaml:"instanceType" validate:"gcp_instance_type"`
StateDiskType string `yaml:"stateDiskType" validate:"oneof=pd-standard pd-balanced pd-ssd"`
DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"`
Measurements measurements.M `yaml:"measurements" validate:"required,no_placeholders"`
}
// OpenStackConfig holds config information for OpenStack based Constellation deployments.
type OpenStackConfig struct {
Cloud string `yaml:"cloud"`
AvailabilityZone string `yaml:"availabilityZone" validate:"required"`
FlavorID string `yaml:"flavorID" validate:"required"`
FloatingIPPoolID string `yaml:"floatingIPPoolID" validate:"required"`
AuthURL string `yaml:"authURL" validate:"required"`
ProjectID string `yaml:"projectID" validate:"required"`
ProjectName string `yaml:"projectName" validate:"required"`
UserDomainName string `yaml:"userDomainName" validate:"required"`
ProjectDomainName string `yaml:"projectDomainName" validate:"required"`
RegionName string `yaml:"regionName" validate:"required"`
Username string `yaml:"username" validate:"required"`
Password string `yaml:"password"`
DirectDownload *bool `yaml:"directDownload" validate:"required"`
Measurements measurements.M `yaml:"measurements" validate:"required,no_placeholders"`
}
// QEMUConfig holds config information for QEMU based Constellation deployments.
type QEMUConfig struct {
ImageFormat string `yaml:"imageFormat" validate:"oneof=qcow2 raw"`
VCPUs int `yaml:"vcpus" validate:"required"`
Memory int `yaml:"memory" validate:"required"`
MetadataAPIImage string `yaml:"metadataAPIServer" validate:"required"`
LibvirtURI string `yaml:"libvirtSocket"`
LibvirtContainerImage string `yaml:"libvirtContainerImage"`
NVRAM string `yaml:"nvram" validate:"required"`
Firmware string `yaml:"firmware"`
Measurements measurements.M `yaml:"measurements" validate:"required,no_placeholders"`
}
// IDKeyDigestEnforcement is the legacy format of idkeydigest.Enforcement.
type IDKeyDigestEnforcement uint32
const (
// Unknown is reserved for invalid configurations.
Unknown IDKeyDigestEnforcement = iota
// StrictChecking will return an error if the ID key digest is not found in the expected list.
StrictChecking
// MAAFallback attempts to verify the attestation using Microsoft Azure Attestation (MAA),
// if the ID key digest is not found in the expected list.
MAAFallback
// WarnOnly logs a warning if the ID key digest is not found in the expected list.
// No error is returned.
WarnOnly
)
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (e *IDKeyDigestEnforcement) UnmarshalYAML(unmarshal func(any) error) error {
return e.unmarshal(unmarshal)
}
func (e *IDKeyDigestEnforcement) unmarshal(unmarshalFunc func(any) error) error {
// Check for legacy format: IDKeyDigestEnforcement might be a boolean.
// If set to true, the value will be set to StrictChecking.
// If set to false, the value will be set to WarnOnly.
var legacyEnforce bool
legacyErr := unmarshalFunc(&legacyEnforce)
if legacyErr == nil {
if legacyEnforce {
*e = StrictChecking
} else {
*e = WarnOnly
}
return nil
}
var enforce string
if err := unmarshalFunc(&enforce); err != nil {
return errors.Join(
err,
fmt.Errorf("trying legacy format: %w", legacyErr),
)
}
*e = e.enforcePolicyFromString(enforce)
if *e == Unknown {
return fmt.Errorf("unknown EnforceIDKeyDigest value: %q", enforce)
}
return nil
}
// enforcePolicyFromString returns IDKeyDigestEnforcement from string.
func (e *IDKeyDigestEnforcement) enforcePolicyFromString(s string) IDKeyDigestEnforcement {
s = strings.ToLower(s)
switch s {
case "strictchecking":
return StrictChecking
case "maafallback":
return MAAFallback
case "warnonly":
return WarnOnly
default:
return Unknown
}
}
// V2ToV3 converts an existing v2 config to a v3 config.
func V2ToV3(path string, fileHandler file.Handler) error {
// Read old format
var cfgV2 Config
if err := fileHandler.ReadYAML(path, &cfgV2); err != nil {
return fmt.Errorf("reading config file %s using v2 format: %w", path, err)
}
// Migrate to new format
var cfgV3 config.Config
cfgV3.Version = config.Version3
cfgV3.Image = cfgV2.Image
cfgV3.Name = cfgV2.Name
cfgV3.StateDiskSizeGB = cfgV2.StateDiskSizeGB
cfgV3.KubernetesVersion = cfgV2.KubernetesVersion
cfgV3.MicroserviceVersion = cfgV2.MicroserviceVersion
cfgV3.DebugCluster = cfgV2.DebugCluster
switch {
case cfgV2.Provider.AWS != nil:
cfgV3.Provider.AWS = &config.AWSConfig{
Region: cfgV2.Provider.AWS.Region,
Zone: cfgV2.Provider.AWS.Zone,
InstanceType: cfgV2.Provider.AWS.InstanceType,
StateDiskType: cfgV2.Provider.AWS.StateDiskType,
IAMProfileControlPlane: cfgV2.Provider.AWS.IAMProfileControlPlane,
IAMProfileWorkerNodes: cfgV2.Provider.AWS.IAMProfileWorkerNodes,
}
cfgV3.Attestation.AWSNitroTPM = &config.AWSNitroTPM{
Measurements: cfgV2.Provider.AWS.Measurements,
}
case cfgV2.Provider.Azure != nil:
cfgV3.Provider.Azure = &config.AzureConfig{
SubscriptionID: cfgV2.Provider.Azure.SubscriptionID,
TenantID: cfgV2.Provider.Azure.TenantID,
Location: cfgV2.Provider.Azure.Location,
ResourceGroup: cfgV2.Provider.Azure.ResourceGroup,
UserAssignedIdentity: cfgV2.Provider.Azure.UserAssignedIdentity,
InstanceType: cfgV2.Provider.Azure.InstanceType,
StateDiskType: cfgV2.Provider.Azure.StateDiskType,
DeployCSIDriver: cfgV2.Provider.Azure.DeployCSIDriver,
SecureBoot: cfgV2.Provider.Azure.SecureBoot,
}
// If an attestation war explicitly set to AzureTrustedLaunch,
// generate a config for AzureTrustedLaunch. Otherwise, generate one for AzureSEVSNP.
if attestVariant, err := variant.FromString(cfgV2.AttestationVariant); err == nil && attestVariant.Equal(variant.AzureTrustedLaunch{}) {
cfgV3.Attestation.AzureTrustedLaunch = &config.AzureTrustedLaunch{
Measurements: cfgV2.Provider.Azure.Measurements,
}
} else {
cfgV3.Attestation.AzureSEVSNP = config.DefaultForAzureSEVSNP()
cfgV3.Attestation.AzureSEVSNP.Measurements = cfgV2.Provider.Azure.Measurements
cfgV3.Attestation.AzureSEVSNP.FirmwareSignerConfig = config.SNPFirmwareSignerConfig{
AcceptedKeyDigests: cfgV2.Provider.Azure.IDKeyDigest,
EnforcementPolicy: idkeydigest.Enforcement(cfgV2.Provider.Azure.EnforceIDKeyDigest),
}
}
case cfgV2.Provider.GCP != nil:
cfgV3.Provider.GCP = &config.GCPConfig{
Project: cfgV2.Provider.GCP.Project,
Region: cfgV2.Provider.GCP.Region,
Zone: cfgV2.Provider.GCP.Zone,
ServiceAccountKeyPath: cfgV2.Provider.GCP.ServiceAccountKeyPath,
InstanceType: cfgV2.Provider.GCP.InstanceType,
StateDiskType: cfgV2.Provider.GCP.StateDiskType,
DeployCSIDriver: cfgV2.Provider.GCP.DeployCSIDriver,
}
cfgV3.Attestation.GCPSEVES = &config.GCPSEVES{
Measurements: cfgV2.Provider.GCP.Measurements,
}
case cfgV2.Provider.OpenStack != nil:
cfgV3.Provider.OpenStack = &config.OpenStackConfig{
Cloud: cfgV2.Provider.OpenStack.Cloud,
AvailabilityZone: cfgV2.Provider.OpenStack.AvailabilityZone,
FlavorID: cfgV2.Provider.OpenStack.FlavorID,
FloatingIPPoolID: cfgV2.Provider.OpenStack.FloatingIPPoolID,
AuthURL: cfgV2.Provider.OpenStack.AuthURL,
ProjectID: cfgV2.Provider.OpenStack.ProjectID,
ProjectName: cfgV2.Provider.OpenStack.ProjectName,
UserDomainName: cfgV2.Provider.OpenStack.UserDomainName,
ProjectDomainName: cfgV2.Provider.OpenStack.ProjectDomainName,
RegionName: cfgV2.Provider.OpenStack.RegionName,
Username: cfgV2.Provider.OpenStack.Username,
Password: cfgV2.Provider.OpenStack.Password,
DirectDownload: cfgV2.Provider.OpenStack.DirectDownload,
}
cfgV3.Attestation.QEMUVTPM = &config.QEMUVTPM{
Measurements: cfgV2.Provider.OpenStack.Measurements,
}
case cfgV2.Provider.QEMU != nil:
cfgV3.Provider.QEMU = &config.QEMUConfig{
ImageFormat: cfgV2.Provider.QEMU.ImageFormat,
VCPUs: cfgV2.Provider.QEMU.VCPUs,
Memory: cfgV2.Provider.QEMU.Memory,
MetadataAPIImage: cfgV2.Provider.QEMU.MetadataAPIImage,
LibvirtURI: cfgV2.Provider.QEMU.LibvirtURI,
LibvirtContainerImage: cfgV2.Provider.QEMU.LibvirtContainerImage,
NVRAM: cfgV2.Provider.QEMU.NVRAM,
Firmware: cfgV2.Provider.QEMU.Firmware,
}
cfgV3.Attestation.QEMUVTPM = &config.QEMUVTPM{
Measurements: cfgV2.Provider.QEMU.Measurements,
}
}
// Create backup
if err := os.Rename(path, path+".backup.v2"); err != nil {
return fmt.Errorf("creating backup: %w", err)
}
// Write migrated config
if err := fileHandler.WriteYAML(path, cfgV3, file.OptOverwrite); err != nil {
return fmt.Errorf("writing %s: %w", path, err)
}
return nil
}