Skip to content

Commit 5744fea

Browse files
Merge pull request openshift#7520 from rna-afk/azure_storage_encryption
CORS-2845: azure: Enable storage account encryption
2 parents 1c0d4dc + 6077dd1 commit 5744fea

File tree

11 files changed

+291
-7
lines changed

11 files changed

+291
-7
lines changed

data/data/azure/bootstrap/main.tf

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,16 @@ data "azurerm_storage_account_sas" "ignition" {
5656
}
5757

5858
resource "azurerm_storage_container" "ignition" {
59-
name = "ignition"
60-
storage_account_name = var.storage_account_name
61-
container_access_type = "private"
59+
name = "ignition"
60+
storage_account_name = var.storage_account_name
6261
}
6362

6463
resource "azurerm_storage_blob" "ignition" {
6564
name = "bootstrap.ign"
6665
source = var.ignition_bootstrap_file
6766
storage_account_name = var.storage_account_name
6867
storage_container_name = azurerm_storage_container.ignition.name
69-
type = "Block"
68+
type = var.azure_keyvault_key_name != "" ? "Page" : "Block"
7069
}
7170

7271
data "ignition_config" "redirect" {

data/data/azure/variables-azure.tf

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,27 @@ variable "azure_master_virtualized_trusted_platform_module" {
278278
description = "Defines whether the instance should have vTPM enabled."
279279
default = ""
280280
}
281+
282+
variable "azure_keyvault_resource_group" {
283+
type = string
284+
description = "Defines the resource group of the key vault used for storage account encryption."
285+
default = ""
286+
}
287+
288+
variable "azure_keyvault_name" {
289+
type = string
290+
description = "Defines the name of the key vault used for storage account encryption."
291+
default = ""
292+
}
293+
294+
variable "azure_keyvault_key_name" {
295+
type = string
296+
description = "Defines the key in the key vault used for storage account encryption."
297+
default = ""
298+
}
299+
300+
variable "azure_user_assigned_identity_key" {
301+
type = string
302+
description = "Defines the user identity key used for the encryption of storage account."
303+
default = ""
304+
}

data/data/azure/vnet/main.tf

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,52 @@ data "azurerm_resource_group" "network" {
4343
name = var.azure_network_resource_group_name
4444
}
4545

46+
data "azurerm_key_vault" "keyvault" {
47+
count = var.azure_keyvault_name != "" ? 1 : 0
48+
49+
name = var.azure_keyvault_name
50+
resource_group_name = var.azure_keyvault_resource_group
51+
}
52+
53+
data "azurerm_key_vault_key" "keyvault_key" {
54+
count = var.azure_keyvault_name != "" ? 1 : 0
55+
56+
name = var.azure_keyvault_key_name
57+
key_vault_id = data.azurerm_key_vault.keyvault[0].id
58+
}
59+
60+
data "azurerm_user_assigned_identity" "keyvault_identity" {
61+
count = var.azure_keyvault_name != "" ? 1 : 0
62+
63+
resource_group_name = var.azure_keyvault_resource_group
64+
name = var.azure_user_assigned_identity_key
65+
}
66+
4667
resource "azurerm_storage_account" "cluster" {
4768
name = "cluster${var.random_storage_account_suffix}"
4869
resource_group_name = data.azurerm_resource_group.main.name
4970
location = var.azure_region
50-
account_tier = "Standard"
71+
account_tier = var.azure_keyvault_name != "" ? "Premium" : "Standard"
5172
account_replication_type = "LRS"
5273
min_tls_version = contains(local.environments_with_min_tls_version, var.azure_environment) ? "TLS1_2" : null
53-
allow_nested_items_to_be_public = false
74+
allow_nested_items_to_be_public = var.azure_keyvault_name != "" ? true : false
5475
tags = var.azure_extra_tags
76+
77+
dynamic "customer_managed_key" {
78+
for_each = var.azure_keyvault_name != "" ? [1] : []
79+
content {
80+
key_vault_key_id = data.azurerm_key_vault_key.keyvault_key[0].id
81+
user_assigned_identity_id = data.azurerm_user_assigned_identity.keyvault_identity[0].id
82+
}
83+
}
84+
85+
dynamic identity {
86+
for_each = var.azure_keyvault_name != "" ? [1] : []
87+
content {
88+
type = "UserAssigned"
89+
identity_ids = [data.azurerm_user_assigned_identity.keyvault_identity[0].id]
90+
}
91+
}
5592
}
5693

5794
resource "azurerm_user_assigned_identity" "main" {

data/data/install.openshift.io_installconfigs.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2481,6 +2481,34 @@ spec:
24812481
description: ControlPlaneSubnet specifies an existing subnet for
24822482
use by the control plane nodes
24832483
type: string
2484+
customerManagedKey:
2485+
description: CustomerManagedKey has the keys needed to encrypt
2486+
the storage account.
2487+
properties:
2488+
keyVault:
2489+
description: KeyVault is the keyvault used for the customer
2490+
created key required for encryption.
2491+
properties:
2492+
keyName:
2493+
description: KeyName is the name of the key vault key.
2494+
type: string
2495+
name:
2496+
description: Name is the name of the key vault.
2497+
type: string
2498+
resourceGroup:
2499+
description: ResourceGroup defines the Azure resource
2500+
group used by the key vault.
2501+
type: string
2502+
required:
2503+
- keyName
2504+
- name
2505+
- resourceGroup
2506+
type: object
2507+
userAssignedIdentityKey:
2508+
description: UserAssignedIdentityKey is the name of the user
2509+
identity that has access to the managed key.
2510+
type: string
2511+
type: object
24842512
defaultMachinePlatform:
24852513
description: DefaultMachinePlatform is the default configuration
24862514
used when installing on Azure for machine pools which do not

pkg/asset/cluster/tfvars.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
182182
}
183183
}
184184

185+
lengthBootstrapFile := int64(len(bootstrapIgn))
186+
if installConfig.Config.Platform.Azure != nil && installConfig.Config.Platform.Azure.CustomerManagedKey != nil &&
187+
installConfig.Config.Platform.Azure.CustomerManagedKey.UserAssignedIdentityKey != "" {
188+
if lengthBootstrapFile%512 != 0 {
189+
lengthBootstrapFile = (((lengthBootstrapFile / 512) + 1) * 512)
190+
}
191+
}
192+
185193
data, err := tfvars.TFVars(
186194
clusterID.InfraID,
187195
installConfig.Config.ClusterDomain(),
@@ -191,6 +199,7 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
191199
useIPv4,
192200
useIPv6,
193201
bootstrapIgn,
202+
lengthBootstrapFile,
194203
masterIgn,
195204
masterCount,
196205
mastersSchedulable,
@@ -383,6 +392,16 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
383392
bootstrapIgnStub = string(shim)
384393
}
385394

395+
managedKeys := azure.CustomerManagedKey{}
396+
if installConfig.Config.Azure.CustomerManagedKey != nil {
397+
managedKeys.KeyVault = azure.KeyVault{
398+
ResourceGroup: installConfig.Config.Azure.CustomerManagedKey.KeyVault.ResourceGroup,
399+
Name: installConfig.Config.Azure.CustomerManagedKey.KeyVault.Name,
400+
KeyName: installConfig.Config.Azure.CustomerManagedKey.KeyVault.KeyName,
401+
}
402+
managedKeys.UserAssignedIdentityKey = installConfig.Config.Azure.CustomerManagedKey.UserAssignedIdentityKey
403+
}
404+
386405
data, err := azuretfvars.TFVars(
387406
azuretfvars.TFVarsSources{
388407
Auth: auth,
@@ -402,6 +421,8 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
402421
HyperVGeneration: hyperVGeneration,
403422
VMArchitecture: installConfig.Config.ControlPlane.Architecture,
404423
InfrastructureName: clusterID.InfraID,
424+
KeyVault: managedKeys.KeyVault,
425+
UserAssignedIdentityKey: managedKeys.UserAssignedIdentityKey,
405426
},
406427
)
407428
if err != nil {

pkg/explain/printer_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ func Test_PrintFields(t *testing.T) {
213213
controlPlaneSubnet <string>
214214
ControlPlaneSubnet specifies an existing subnet for use by the control plane nodes
215215
216+
customerManagedKey <object>
217+
CustomerManagedKey has the keys needed to encrypt the storage account.
218+
216219
defaultMachinePlatform <object>
217220
DefaultMachinePlatform is the default configuration used when installing on Azure for machine pools which do not define their own platform configuration.
218221

pkg/tfvars/azure/azure.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ type config struct {
6969
SecureVirtualMachineDiskEncryptionSetID string `json:"azure_master_secure_vm_disk_encryption_set_id,omitempty"`
7070
SecureBoot string `json:"azure_master_secure_boot,omitempty"`
7171
VirtualizedTrustedPlatformModule string `json:"azure_master_virtualized_trusted_platform_module,omitempty"`
72+
KeyVaultResourceGroup string `json:"azure_keyvault_resource_group,omitempty"`
73+
KeyVaultName string `json:"azure_keyvault_name,omitempty"`
74+
KeyVaultKeyName string `json:"azure_keyvault_key_name,omitempty"`
75+
UserAssignedIdentity string `json:"azure_user_assigned_identity_key,omitempty"`
7276
}
7377

7478
// TFVarsSources contains the parameters to be converted into Terraform variables
@@ -90,6 +94,8 @@ type TFVarsSources struct {
9094
HyperVGeneration string
9195
VMArchitecture types.Architecture
9296
InfrastructureName string
97+
KeyVault azure.KeyVault
98+
UserAssignedIdentityKey string
9399
}
94100

95101
// TFVars generates Azure-specific Terraform variables launching the cluster.
@@ -187,6 +193,10 @@ func TFVars(sources TFVarsSources) ([]byte, error) {
187193
SecureVirtualMachineDiskEncryptionSetID: masterConfig.OSDisk.ManagedDisk.SecurityProfile.DiskEncryptionSet.ID,
188194
SecureBoot: secureBoot,
189195
VirtualizedTrustedPlatformModule: virtualizedTrustedPlatformModule,
196+
KeyVaultResourceGroup: sources.KeyVault.ResourceGroup,
197+
KeyVaultName: sources.KeyVault.Name,
198+
KeyVaultKeyName: sources.KeyVault.KeyName,
199+
UserAssignedIdentity: sources.UserAssignedIdentityKey,
190200
}
191201

192202
return json.MarshalIndent(cfg, "", " ")

pkg/tfvars/tfvars.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,21 @@ type Config struct {
2828
}
2929

3030
// TFVars generates terraform.tfvar JSON for launching the cluster.
31-
func TFVars(clusterID string, clusterDomain string, baseDomain string, machineV4CIDRs []string, machineV6CIDRs []string, useIPv4, useIPv6 bool, bootstrapIgn string, masterIgn string, masterCount int, mastersSchedulable bool) ([]byte, error) {
31+
func TFVars(clusterID string, clusterDomain string, baseDomain string, machineV4CIDRs []string, machineV6CIDRs []string, useIPv4, useIPv6 bool, bootstrapIgn string, bootstrapIgnSize int64, masterIgn string, masterCount int, mastersSchedulable bool) ([]byte, error) {
3232
f, err := os.CreateTemp("", "openshift-install-bootstrap-*.ign")
3333
if err != nil {
3434
return nil, errors.Wrap(err, "failed to create tmp file for bootstrap ignition")
3535
}
3636
defer f.Close()
3737

38+
// In azure, if the storage account is encrypted, page blob is used instead of block blob due to lack of support.
39+
// Page blobs file size must be a multiple of 512, hence a little padding is needed to push the file.
40+
// Finding the nearest size divisible by 512 and adding that padding to the file.
41+
// Since the file is json type, padding at the end results in json parsing error at bootstrap ignition.
42+
// Adding the paddding to just before the last } in the json file to bypass the parsing error.
43+
padding := bootstrapIgnSize - int64(len(bootstrapIgn))
44+
bootstrapIgn = bootstrapIgn[0:len(bootstrapIgn)-1] + strings.Repeat(" ", int(padding)) + string(bootstrapIgn[len(bootstrapIgn)-1])
45+
3846
if _, err := f.WriteString(bootstrapIgn); err != nil {
3947
return nil, errors.Wrap(err, "failed to write bootstrap ignition")
4048
}

pkg/types/azure/platform.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,28 @@ type Platform struct {
9797
// Resources created by the cluster itself may not include these tags.
9898
// +optional
9999
UserTags map[string]string `json:"userTags,omitempty"`
100+
101+
// CustomerManagedKey has the keys needed to encrypt the storage account.
102+
CustomerManagedKey *CustomerManagedKey `json:"customerManagedKey,omitempty"`
103+
}
104+
105+
// KeyVault defines an Azure Key Vault.
106+
type KeyVault struct {
107+
// ResourceGroup defines the Azure resource group used by the key
108+
// vault.
109+
ResourceGroup string `json:"resourceGroup"`
110+
// Name is the name of the key vault.
111+
Name string `json:"name"`
112+
// KeyName is the name of the key vault key.
113+
KeyName string `json:"keyName"`
114+
}
115+
116+
// CustomerManagedKey defines the customer managed key settings for encryption of the Azure storage account.
117+
type CustomerManagedKey struct {
118+
// KeyVault is the keyvault used for the customer created key required for encryption.
119+
KeyVault KeyVault `json:"keyVault,omitempty"`
120+
// UserAssignedIdentityKey is the name of the user identity that has access to the managed key.
121+
UserAssignedIdentityKey string `json:"userAssignedIdentityKey,omitempty"`
100122
}
101123

102124
// CloudEnvironment is the name of the Azure cloud environment

pkg/types/azure/validation/platform.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ var (
4040

4141
// tagKeyPrefixRegex is for verifying that the tag value does not contain restricted prefixes.
4242
tagKeyPrefixRegex = regexp.MustCompile(`^(?i)(name$|kubernetes\.io|openshift\.io|microsoft|azure|windows)`)
43+
44+
// keyVaultNameRegex is for verifying the name of the key vault used for storage account encryption.
45+
keyVaultNameRegex = regexp.MustCompile(`^[A-Za-z][0-9A-Za-z-]{1,22}[A-Za-z0-9]$`)
46+
47+
// keyVaultKeyNameRegex is for verifying the key name of the key vault used for storage account encryption.
48+
keyVaultKeyNameRegex = regexp.MustCompile(`^[0-9A-Za-z-]{1,127}$`)
49+
50+
// keyVaultUserAssignedIdentityRegex is for verifying the user assigned identity key used for storage account encryption.
51+
keyVaultUserAssignedIdentityRegex = regexp.MustCompile(`^[0-9A-Za-z][0-9A-Za-z-_]{2,127}$`)
4352
)
4453

4554
// maxUserTagLimit is the maximum userTags that can be configured as defined in openshift/api.
@@ -99,6 +108,10 @@ func ValidatePlatform(p *azure.Platform, publish types.PublishingStrategy, fldPa
99108
}
100109
}
101110

111+
if p.CustomerManagedKey != nil {
112+
allErrs = append(allErrs, validateCustomerManagedKeys(p.CloudName, *p.CustomerManagedKey, fldPath.Child("customerManagedKey"))...)
113+
}
114+
102115
// support for Azure user-defined tags made available through
103116
// RFE-2017 is for AzurePublicCloud only.
104117
if p.CloudName != azure.PublicCloud && len(p.UserTags) > 0 {
@@ -122,6 +135,41 @@ func ValidatePlatform(p *azure.Platform, publish types.PublishingStrategy, fldPa
122135
return allErrs
123136
}
124137

138+
// validateCustomerManagedKeys validates the key vault id.
139+
func validateCustomerManagedKeys(cloudName azure.CloudEnvironment, s azure.CustomerManagedKey, fldPath *field.Path) field.ErrorList {
140+
var allErrs field.ErrorList
141+
142+
if cloudName == azure.StackCloud {
143+
return append(allErrs, field.Invalid(fldPath, s.KeyVault.Name, "storage account encryption is not supported on this platform"))
144+
}
145+
146+
if s.KeyVault.KeyName == "" {
147+
allErrs = append(allErrs, field.Required(fldPath, "key vault key name is required for storage account encryption"))
148+
} else if !keyVaultKeyNameRegex.MatchString(s.KeyVault.KeyName) {
149+
allErrs = append(allErrs, field.Invalid(fldPath, s.KeyVault.KeyName, "invalid key name for encryption"))
150+
}
151+
152+
if s.KeyVault.Name == "" {
153+
allErrs = append(allErrs, field.Required(fldPath, "name of the key vault is required for storage account encryption"))
154+
} else if !keyVaultNameRegex.MatchString(s.KeyVault.Name) || strings.Contains(s.KeyVault.Name, "--") {
155+
allErrs = append(allErrs, field.Invalid(fldPath, s.KeyVault.Name, "invalid name for key vault for encryption"))
156+
}
157+
158+
if s.KeyVault.ResourceGroup == "" {
159+
allErrs = append(allErrs, field.Required(fldPath, "resource group of the key vault is required for storage account encryption"))
160+
} else if !RxResourceGroup.MatchString(s.KeyVault.ResourceGroup) {
161+
allErrs = append(allErrs, field.Invalid(fldPath, s.KeyVault.ResourceGroup, "invalid resource group for encryption"))
162+
}
163+
164+
if s.UserAssignedIdentityKey == "" {
165+
allErrs = append(allErrs, field.Required(fldPath, "user assigned identity key is required for storage account encryption"))
166+
} else if !keyVaultUserAssignedIdentityRegex.MatchString(s.UserAssignedIdentityKey) {
167+
allErrs = append(allErrs, field.Invalid(fldPath, s.UserAssignedIdentityKey, "invalid user assigned identity key for encryption"))
168+
}
169+
170+
return allErrs
171+
}
172+
125173
// validateUserTags verifies if configured number of UserTags is not more than
126174
// allowed limit and the tag keys and values are valid.
127175
func validateUserTags(tags map[string]string, fldPath *field.Path) field.ErrorList {

0 commit comments

Comments
 (0)