Skip to content

Commit

Permalink
azurerm_attestation_provider - support policy management (#20972)
Browse files Browse the repository at this point in the history
  • Loading branch information
wuxu92 authored Apr 21, 2023
1 parent d557502 commit 5035ae6
Show file tree
Hide file tree
Showing 19 changed files with 1,625 additions and 4 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/dave/jennifer v1.6.0
github.com/davecgh/go-spew v1.1.1
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang-jwt/jwt/v4 v4.4.3
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.1.2
github.com/hashicorp/go-azure-helpers v0.55.0
Expand Down Expand Up @@ -40,7 +41,6 @@ require (
github.com/apparentlymart/go-textseg v1.0.0 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions internal/clients/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ func Build(ctx context.Context, builder ClientBuilder) (*Client, error) {
return nil, fmt.Errorf("unable to build authorizer for Storage API: %+v", err)
}

attestationAuth, err := auth.NewAuthorizerFromCredentials(ctx, *builder.AuthConfig, builder.AuthConfig.Environment.Attestation)
if err != nil {
return nil, fmt.Errorf("unable to build authorizer for Attestation API: %+v", err)
}

keyVaultAuth, err = auth.NewAuthorizerFromCredentials(ctx, *builder.AuthConfig, builder.AuthConfig.Environment.KeyVault)
if err != nil {
return nil, fmt.Errorf("unable to build authorizer for Key Vault API: %+v", err)
Expand Down Expand Up @@ -135,6 +140,7 @@ func Build(ctx context.Context, builder ClientBuilder) (*Client, error) {
PartnerId: builder.PartnerID,
TerraformVersion: builder.TerraformVersion,

AttestationAuthorizer: authWrapper.AutorestAuthorizer(attestationAuth).BearerAuthorizerCallback(),
BatchManagementAuthorizer: authWrapper.AutorestAuthorizer(batchManagementAuth),
KeyVaultAuthorizer: authWrapper.AutorestAuthorizer(keyVaultAuth).BearerAuthorizerCallback(),
ResourceManagerAuthorizer: authWrapper.AutorestAuthorizer(resourceManagerAuth),
Expand Down
1 change: 1 addition & 0 deletions internal/common/client_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type ClientOptions struct {
ResourceManagerEndpoint string

// Legacy authorizers for go-autorest
AttestationAuthorizer autorest.Authorizer
BatchManagementAuthorizer autorest.Authorizer
KeyVaultAuthorizer autorest.Authorizer
ResourceManagerAuthorizer autorest.Authorizer
Expand Down
101 changes: 98 additions & 3 deletions internal/services/attestation/attestation_provider_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"encoding/pem"
"fmt"
"log"
"regexp"
"time"

"github.com/Azure/azure-sdk-for-go/services/attestation/2020-10-01/attestation"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
Expand All @@ -16,6 +18,7 @@ import (
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/attestation/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
"github.com/hashicorp/terraform-provider-azurerm/internal/timeouts"
)

Expand Down Expand Up @@ -68,10 +71,40 @@ func resourceAttestationProvider() *pluginsdk.Resource {
Type: pluginsdk.TypeString,
Computed: true,
},

"policy": {
Type: pluginsdk.TypeList,
Optional: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
// one type MUST have only one policy as most, add this validation in Create/Update
"environment_type": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
string(attestation.OpenEnclave),
string(attestation.SgxEnclave),
string(attestation.Tpm),
}, false),
},

"data": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile(`[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*`), ""),
},
},
},
},
},
}
}

type policyDef struct {
Type attestation.Type
Data string
}

func resourceAttestationProviderCreate(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Attestation.ProviderClient
subscriptionId := meta.(*clients.Client).Account.SubscriptionId
Expand All @@ -92,6 +125,12 @@ func resourceAttestationProviderCreate(d *pluginsdk.ResourceData, meta interface
return tf.ImportAsExistsError("azurerm_attestation_provider", id.ID())
}

// each type of policy should have 1 item at most.
policies, err := expandPolicies(d.Get("policy").([]interface{}))
if err != nil {
return fmt.Errorf("configuration in policy invalid: %+v", err)
}

props := attestationproviders.AttestationServiceCreationParams{
Location: location.Normalize(d.Get("location").(string)),
Properties: attestationproviders.AttestationServiceCreationSpecificParams{
Expand All @@ -115,10 +154,22 @@ func resourceAttestationProviderCreate(d *pluginsdk.ResourceData, meta interface
props.Properties.PolicySigningCertificates = expandArmAttestationProviderJSONWebKeySet(v)
}

if _, err := client.Create(ctx, id, props); err != nil {
resp, err := client.Create(ctx, id, props)
if err != nil {
return fmt.Errorf("creating %s: %+v", id, err)
}

// set policies
if resp.Model != nil && resp.Model.Properties != nil && resp.Model.Properties.AttestUri != nil {
url := *resp.Model.Properties.AttestUri
cli := meta.(*clients.Client).Attestation.PolicyClient
for _, policy := range policies {
if _, err = cli.Set(ctx, url, policy.Type, policy.Data); err != nil {
return fmt.Errorf("set policy: %+v", err)
}
}
}

d.SetId(id.ID())
return resourceAttestationProviderRead(d, meta)
}
Expand Down Expand Up @@ -169,13 +220,36 @@ func resourceAttestationProviderUpdate(d *pluginsdk.ResourceData, meta interface
return err
}

var hasChange bool
updateParams := attestationproviders.AttestationServicePatchParams{}
if d.HasChange("tags") {
hasChange = true
updateParams.Tags = tags.Expand(d.Get("tags").(map[string]interface{}))
}

if _, err := client.Update(ctx, *id, updateParams); err != nil {
return fmt.Errorf("updating %s: %+v", *id, err)
if hasChange {
if _, err := client.Update(ctx, *id, updateParams); err != nil {
return fmt.Errorf("updating %s: %+v", *id, err)
}
}

if d.HasChange("policy") {
policies, err := expandPolicies(d.Get("policy").([]interface{}))
if err != nil {
return fmt.Errorf("expand policies: %+v", err)
}
policyClient := meta.(*clients.Client).Attestation.PolicyClient

url := d.Get("attestation_uri").(string)
if url == "" {
log.Printf("[Warn] got empty attestation instance url")
} else {
for _, policy := range policies {
if _, err = policyClient.Set(ctx, url, policy.Type, policy.Data); err != nil {
return fmt.Errorf("set policy in %s: %+v", url, err)
}
}
}
}
return resourceAttestationProviderRead(d, meta)
}
Expand Down Expand Up @@ -221,3 +295,24 @@ func expandArmAttestationProviderJSONWebKeyArray(pem string) *[]attestationprovi

return &results
}

func expandPolicies(input []interface{}) (res []policyDef, err error) {
for _, ins := range input {
if ins == nil {
continue
}
policy := ins.(map[string]interface{})
typ := attestation.Type(policy["environment_type"].(string))

for _, def := range res {
if def.Type == typ {
return nil, fmt.Errorf("repeated policy environment_type: %s", typ)
}
}
res = append(res, policyDef{
Type: typ,
Data: policy["data"].(string),
})
}
return
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/pem"
"fmt"
"math/big"
"strings"
"testing"
"time"

"github.com/golang-jwt/jwt/v4"

"github.com/hashicorp/go-azure-sdk/resource-manager/attestation/2020-10-01/attestationproviders"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check"
Expand Down Expand Up @@ -95,6 +98,47 @@ func TestAccAttestationProvider_completeFile(t *testing.T) {
})
}

func TestAccAttestationProvider_WithPolicy(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_attestation_provider", "test")
r := AttestationProviderResource{}
randStr := strings.ToLower(acceptance.RandString(10))

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.withPolicy(data, randStr),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
// must ignore policy_signing_certificate since the API does not return these values
data.ImportStep("policy_signing_certificate", "policy"),
})
}

func TestAccAttestationProvider_WithPolicyUpdate(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_attestation_provider", "test")
r := AttestationProviderResource{}
randStr := strings.ToLower(acceptance.RandString(10))

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data, randStr),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.withPolicy(data, randStr),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
// must ignore policy_signing_certificate since the API does not return these values
data.ImportStep("policy_signing_certificate", "policy"),
})
}

func TestAccAttestationProvider_update(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_attestation_provider", "test")
r := AttestationProviderResource{}
Expand Down Expand Up @@ -270,3 +314,44 @@ resource "azurerm_attestation_provider" "test" {
}
`, template, randStr)
}

func (a AttestationProviderResource) withPolicy(data acceptance.TestData, randStr string) string {
// do not set `policy_signing_certificate_data`, for policy will use `jwt.SigningMethodNone`
template := AttestationProviderResource{}.template(data)
return fmt.Sprintf(`
%s
resource "azurerm_attestation_provider" "test" {
name = "acctestap%s"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
policy {
environment_type = "SgxEnclave"
data = "%s"
}
}
`, template, randStr, a.genJWT())
}

func (AttestationProviderResource) genJWT() string {
// document about create policy: https://learn.microsoft.com/en-us/azure/attestation/author-sign-policy
policyContent := `version=1.0;
authorizationrules
{
[type=="secureBootEnabled", value==true, issuer=="AttestationService"]=>permit();
};
issuancerules
{
=> issue(type="SecurityLevelValue", value=100);
};`
b64Encoded := base64.RawURLEncoding.EncodeToString([]byte(policyContent))
token := jwt.NewWithClaims(jwt.SigningMethodNone, jwt.MapClaims{
"AttestationPolicy": b64Encoded,
})
token.Header["jku"] = "https://xxx.us.attest.azure.net/certs"
token.Header["kid"] = "xxx"
str, _ := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
return str
}
6 changes: 6 additions & 0 deletions internal/services/attestation/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package client
import (
"fmt"

"github.com/Azure/azure-sdk-for-go/services/attestation/2020-10-01/attestation"
"github.com/hashicorp/go-azure-sdk/resource-manager/attestation/2020-10-01/attestationproviders"
"github.com/hashicorp/terraform-provider-azurerm/internal/common"
)

type Client struct {
ProviderClient *attestationproviders.AttestationProvidersClient
PolicyClient *attestation.PolicyClient
}

func NewClient(o *common.ClientOptions) (*Client, error) {
Expand All @@ -18,7 +20,11 @@ func NewClient(o *common.ClientOptions) (*Client, error) {
}
o.Configure(providerClient.Client, o.Authorizers.ResourceManager)

policyClient := attestation.NewPolicyClient()
o.ConfigureClient(&policyClient.Client, o.AttestationAuthorizer)

return &Client{
PolicyClient: &policyClient,
ProviderClient: providerClient,
}, nil
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5035ae6

Please sign in to comment.