Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: support to declaratively set attestation policy #1954

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions cli/internal/cloudcmd/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ load("//bazel/go:go_test.bzl", "go_test")
go_library(
name = "cloudcmd",
srcs = [
"attestationpolicy.go",
"clients.go",
"cloudcmd.go",
"create.go",
"iam.go",
"patch.go",
"rollback.go",
"terminate.go",
"validators.go",
Expand All @@ -29,9 +29,6 @@ go_library(
"//internal/config",
"//internal/constants",
"//internal/imagefetcher",
"@com_github_azure_azure_sdk_for_go//profiles/latest/attestation/attestation",
"@com_github_azure_azure_sdk_for_go_sdk_azcore//policy",
"@com_github_azure_azure_sdk_for_go_sdk_azidentity//:azidentity",
"@com_github_hashicorp_terraform_json//:terraform-json",
"@com_github_spf13_cobra//:cobra",
],
Expand All @@ -40,10 +37,10 @@ go_library(
go_test(
name = "cloudcmd_test",
srcs = [
"attestationpolicy_test.go",
"clients_test.go",
"create_test.go",
"iam_test.go",
"patch_test.go",
"rollback_test.go",
"terminate_test.go",
"validators_test.go",
Expand Down
54 changes: 54 additions & 0 deletions cli/internal/cloudcmd/attestationpolicy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
Copyright (c) Edgeless Systems GmbH

SPDX-License-Identifier: AGPL-3.0-only
*/
package cloudcmd

import (
"encoding/base64"
"fmt"
)

// maaAttestationPolicy is the default attestation policy for Azure VMs on Constellation.
const maaAttestationPolicy = `
version= 1.0;
authorizationrules
{
[type=="x-ms-azurevm-default-securebootkeysvalidated", value==false] => deny();
[type=="x-ms-azurevm-debuggersdisabled", value==false] => deny();
// The line below was edited by the Constellation CLI. Do not edit manually.
//[type=="secureboot", value==false] => deny();
[type=="x-ms-azurevm-signingdisabled", value==false] => deny();
[type=="x-ms-azurevm-dbvalidated", value==false] => deny();
[type=="x-ms-azurevm-dbxvalidated", value==false] => deny();
=> permit();
};
issuancerules
{
};`

// NewAzureMaaAttestationPolicy returns a new AzureAttestationPolicy to use with MAA.
func NewAzureMaaAttestationPolicy() AzureAttestationPolicy {
return AzureAttestationPolicy{
policy: maaAttestationPolicy,
}
}

// AzureAttestationPolicy patches attestation policies on Azure.
type AzureAttestationPolicy struct {
policy string
}

// Encode encodes the base64-encoded attestation policy in the JWS format specified here:
// https://learn.microsoft.com/en-us/azure/attestation/author-sign-policy#creating-the-policy-file-in-json-web-signature-format
func (p AzureAttestationPolicy) Encode() string {
encodedPolicy := base64.RawURLEncoding.EncodeToString([]byte(p.policy))
const header = `{"alg":"none"}`
payload := fmt.Sprintf(`{"AttestationPolicy":"%s"}`, encodedPolicy)

encodedHeader := base64.RawURLEncoding.EncodeToString([]byte(header))
encodedPayload := base64.RawURLEncoding.EncodeToString([]byte(payload))

return fmt.Sprintf("%s.%s.", encodedHeader, encodedPayload)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import (
"github.com/stretchr/testify/assert"
)

func TestEncodeAttestationPolicy(t *testing.T) {
func TestAzureMaaAttestationPolicyEncode(t *testing.T) {
assert := assert.New(t)

p := AzurePolicyPatcher{}
p := NewAzureMaaAttestationPolicy()

// taken from <resource group url in the azure portal>/providers/Microsoft.Attestation/attestationProviders/<attestation provider name>/mrsg_item2
expected := "eyJhbGciOiJub25lIn0.eyJBdHRlc3RhdGlvblBvbGljeSI6IkNpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCMlpYSnphVzl1UFNBeExqQTdDaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQmhkWFJvYjNKcGVtRjBhVzl1Y25Wc1pYTUtJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lIc0tJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0JiZEhsd1pUMDlJbmd0YlhNdFlYcDFjbVYyYlMxa1pXWmhkV3gwTFhObFkzVnlaV0p2YjNSclpYbHpkbUZzYVdSaGRHVmtJaXdnZG1Gc2RXVTlQV1poYkhObFhTQTlQaUJrWlc1NUtDazdDaUFnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnVzNSNWNHVTlQU0o0TFcxekxXRjZkWEpsZG0wdFpHVmlkV2RuWlhKelpHbHpZV0pzWldRaUxDQjJZV3gxWlQwOVptRnNjMlZkSUQwLUlHUmxibmtvS1RzS0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQXZMeUJVYUdVZ2JHbHVaU0JpWld4dmR5QjNZWE1nWldScGRHVmtJR0o1SUhSb1pTQkRiMjV6ZEdWc2JHRjBhVzl1SUVOTVNTNGdSRzhnYm05MElHVmthWFFnYldGdWRXRnNiSGt1Q2lBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0x5OWJkSGx3WlQwOUluTmxZM1Z5WldKdmIzUWlMQ0IyWVd4MVpUMDlabUZzYzJWZElEMC1JR1JsYm5rb0tUc0tJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0JiZEhsd1pUMDlJbmd0YlhNdFlYcDFjbVYyYlMxemFXZHVhVzVuWkdsellXSnNaV1FpTENCMllXeDFaVDA5Wm1Gc2MyVmRJRDAtSUdSbGJua29LVHNLSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCYmRIbHdaVDA5SW5ndGJYTXRZWHAxY21WMmJTMWtZblpoYkdsa1lYUmxaQ0lzSUhaaGJIVmxQVDFtWVd4elpWMGdQVDRnWkdWdWVTZ3BPd29nSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUZ0MGVYQmxQVDBpZUMxdGN5MWhlblZ5WlhadExXUmllSFpoYkdsa1lYUmxaQ0lzSUhaaGJIVmxQVDFtWVd4elpWMGdQVDRnWkdWdWVTZ3BPd29nSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUQwLUlIQmxjbTFwZENncE93b2dJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ2ZUc0tJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lHbHpjM1ZoYm1ObGNuVnNaWE1LSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJSHNLSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJSDA3In0."
assert.Equal(expected, p.encodeAttestationPolicy())
assert.Equal(expected, p.Encode())
}
15 changes: 1 addition & 14 deletions cli/internal/cloudcmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ type Creator struct {
newTerraformClient func(ctx context.Context) (terraformClient, error)
newLibvirtRunner func() libvirtRunner
newRawDownloader func() rawDownloader
policyPatcher policyPatcher
}

// NewCreator creates a new creator.
Expand All @@ -52,7 +51,6 @@ func NewCreator(out io.Writer) *Creator {
newRawDownloader: func() rawDownloader {
return imagefetcher.NewDownloader()
},
policyPatcher: NewAzurePolicyPatcher(),
}
}

Expand Down Expand Up @@ -226,6 +224,7 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, opts Crea
ImageID: opts.image,
SecureBoot: *opts.Config.Provider.Azure.SecureBoot,
CreateMAA: opts.Config.GetAttestationConfig().GetVariant().Equal(variant.AzureSEVSNP{}),
MAAPolicy: NewAzureMaaAttestationPolicy().Encode(),
Debug: opts.Config.IsDebugCluster(),
}

Expand All @@ -243,13 +242,6 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, opts Crea
return clusterid.File{}, err
}

if vars.CreateMAA {
// Patch the attestation policy to allow the cluster to boot while having secure boot disabled.
if err := c.policyPatcher.Patch(ctx, tfOutput.AttestationURL); err != nil {
return clusterid.File{}, err
}
}

return clusterid.File{
CloudProvider: cloudprovider.Azure,
IP: tfOutput.IP,
Expand All @@ -259,11 +251,6 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, opts Crea
}, nil
}

// policyPatcher interacts with the CSP (currently only applies for Azure) to update the attestation policy.
type policyPatcher interface {
Patch(ctx context.Context, attestationURL string) error
}

// The azurerm Terraform provider enforces its own convention of case sensitivity for Azure URIs which Azure's API itself does not enforce or, even worse, actually returns.
// Let's go loco with case insensitive Regexp here and fix the user input here to be compliant with this arbitrary design decision.
var (
Expand Down
27 changes: 1 addition & 26 deletions cli/internal/cloudcmd/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func TestCreator(t *testing.T) {
libvirt *stubLibvirtRunner
provider cloudprovider.Provider
config *config.Config
policyPatcher *stubPolicyPatcher
wantErr bool
wantRollback bool // Use only together with stubClients.
wantTerraformRollback bool // When libvirt fails, don't call into Terraform.
Expand Down Expand Up @@ -65,7 +64,6 @@ func TestCreator(t *testing.T) {
cfg.RemoveProviderAndAttestationExcept(cloudprovider.Azure)
return cfg
}(),
policyPatcher: &stubPolicyPatcher{},
},
"azure trusted launch": {
tfClient: &stubTerraformClient{ip: ip},
Expand All @@ -77,18 +75,6 @@ func TestCreator(t *testing.T) {
}
return cfg
}(),
policyPatcher: &stubPolicyPatcher{},
},
"azure new policy patch error": {
tfClient: &stubTerraformClient{ip: ip},
provider: cloudprovider.Azure,
config: func() *config.Config {
cfg := config.Default()
cfg.RemoveProviderAndAttestationExcept(cloudprovider.Azure)
return cfg
}(),
policyPatcher: &stubPolicyPatcher{someErr},
wantErr: true,
},
"azure newTerraformClient error": {
newTfClientErr: someErr,
Expand All @@ -98,8 +84,7 @@ func TestCreator(t *testing.T) {
cfg.RemoveProviderAndAttestationExcept(cloudprovider.Azure)
return cfg
}(),
policyPatcher: &stubPolicyPatcher{},
wantErr: true,
wantErr: true,
},
"azure create cluster error": {
tfClient: &stubTerraformClient{createClusterErr: someErr},
Expand All @@ -109,7 +94,6 @@ func TestCreator(t *testing.T) {
cfg.RemoveProviderAndAttestationExcept(cloudprovider.Azure)
return cfg
}(),
policyPatcher: &stubPolicyPatcher{},
wantErr: true,
wantRollback: true,
wantTerraformRollback: true,
Expand Down Expand Up @@ -213,7 +197,6 @@ func TestCreator(t *testing.T) {
destination: "some-destination",
}
},
policyPatcher: tc.policyPatcher,
}

opts := CreateOptions{
Expand Down Expand Up @@ -247,14 +230,6 @@ func TestCreator(t *testing.T) {
}
}

type stubPolicyPatcher struct {
patchErr error
}

func (s stubPolicyPatcher) Patch(_ context.Context, _ string) error {
return s.patchErr
}

func TestNormalizeAzureURIs(t *testing.T) {
testCases := map[string]struct {
in terraform.AzureClusterVariables
Expand Down
94 changes: 0 additions & 94 deletions cli/internal/cloudcmd/patch.go

This file was deleted.

2 changes: 2 additions & 0 deletions cli/internal/cmd/upgradeapply.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"strings"
"time"

"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/cli/internal/kubernetes"
Expand Down Expand Up @@ -253,6 +254,7 @@ func parseTerraformUpgradeVars(cmd *cobra.Command, conf *config.Config, fetcher
ImageID: imageRef,
SecureBoot: *conf.Provider.Azure.SecureBoot,
CreateMAA: conf.GetAttestationConfig().GetVariant().Equal(variant.AzureSEVSNP{}),
MAAPolicy: cloudcmd.NewAzureMaaAttestationPolicy().Encode(),
Debug: conf.IsDebugCluster(),
}
return targets, vars, nil
Expand Down
9 changes: 5 additions & 4 deletions cli/internal/terraform/terraform/azure/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,18 @@ resource "random_password" "initSecret" {
resource "azurerm_attestation_provider" "attestation_provider" {
count = var.create_maa ? 1 : 0
# name must be between 3 and 24 characters in length and use numbers and lower-case letters only.
name = format("constell%s", local.uid)
resource_group_name = var.resource_group
location = var.location
name = format("constell%s", local.uid)
resource_group_name = var.resource_group
location = var.location
azure_vm_policy_base64 = var.maa_policy

lifecycle {
# Attestation policies will be set automatically upon creation, even if not specified in the resource,
# while they aren't being incorporated into the Terraform state correctly.
# To prevent them from being set to null when applying an upgrade, ignore the changes until the issue
# is resolved by Azure.
# Related issue: https://github.com/hashicorp/terraform-provider-azurerm/issues/21998
ignore_changes = [open_enclave_policy_base64, sgx_enclave_policy_base64, tpm_policy_base64]
ignore_changes = [open_enclave_policy_base64, sgx_enclave_policy_base64, tpm_policy_base64, azure_vm_policy_base64, sev_snp_policy_base64]
}
}

Expand Down
5 changes: 5 additions & 0 deletions cli/internal/terraform/terraform/azure/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ variable "create_maa" {
description = "Whether to create a Microsoft Azure attestation provider."
}

variable "maa_policy" {
type = string
description = "Base64-encoded Attestation policy for the Microsoft Azure attestation provider."
}

variable "debug" {
type = bool
default = false
Expand Down
3 changes: 3 additions & 0 deletions cli/internal/terraform/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ type AzureClusterVariables struct {
SecureBoot bool
// CreateMAA sets whether a Microsoft Azure attestation provider should be created.
CreateMAA bool
// MAAPolicy sets the base64-encoded policy for the Microsoft Azure attestation provider.
MAAPolicy string
// Debug is true if debug mode is enabled.
Debug bool
}
Expand All @@ -200,6 +202,7 @@ func (v *AzureClusterVariables) String() string {
writeLinef(b, "confidential_vm = %t", v.ConfidentialVM)
writeLinef(b, "secure_boot = %t", v.SecureBoot)
writeLinef(b, "create_maa = %t", v.CreateMAA)
writeLinef(b, "maa_policy = %q", v.MAAPolicy)
writeLinef(b, "debug = %t", v.Debug)

return b.String()
Expand Down
Loading