diff --git a/azurerm/internal/clients/client.go b/azurerm/internal/clients/client.go index 97e9f9678029..9ed0b26315dd 100644 --- a/azurerm/internal/clients/client.go +++ b/azurerm/internal/clients/client.go @@ -12,6 +12,7 @@ import ( appConfiguration "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appconfiguration/client" applicationInsights "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/applicationinsights/client" appPlatform "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform/client" + attestation "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/attestation/client" authorization "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/authorization/client" automation "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/automation/client" batch "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/batch/client" @@ -97,6 +98,7 @@ type Client struct { AppConfiguration *appConfiguration.Client AppInsights *applicationInsights.Client AppPlatform *appPlatform.Client + Attestation *attestation.Client Authorization *authorization.Client Automation *automation.Client Batch *batch.Client @@ -183,6 +185,7 @@ func (client *Client) Build(ctx context.Context, o *common.ClientOptions) error client.AppConfiguration = appConfiguration.NewClient(o) client.AppInsights = applicationInsights.NewClient(o) client.AppPlatform = appPlatform.NewClient(o) + client.Attestation = attestation.NewClient(o) client.Authorization = authorization.NewClient(o) client.Automation = automation.NewClient(o) client.Batch = batch.NewClient(o) diff --git a/azurerm/internal/provider/services.go b/azurerm/internal/provider/services.go index 49a3b4c9946f..fb13e315327b 100644 --- a/azurerm/internal/provider/services.go +++ b/azurerm/internal/provider/services.go @@ -7,6 +7,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appconfiguration" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/applicationinsights" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/attestation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/authorization" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/automation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/batch" @@ -90,6 +91,7 @@ func SupportedServices() []common.ServiceRegistration { appconfiguration.Registration{}, appplatform.Registration{}, applicationinsights.Registration{}, + attestation.Registration{}, authorization.Registration{}, automation.Registration{}, batch.Registration{}, diff --git a/azurerm/internal/services/attestation/attestation_provider_data_source.go b/azurerm/internal/services/attestation/attestation_provider_data_source.go new file mode 100644 index 000000000000..dfbbc4454bc4 --- /dev/null +++ b/azurerm/internal/services/attestation/attestation_provider_data_source.go @@ -0,0 +1,80 @@ +package attestation + +import ( + "fmt" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func dataSourceAttestationProvider() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmAttestationProviderRead, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(5 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "resource_group_name": azure.SchemaResourceGroupNameForDataSource(), + + "location": azure.SchemaLocationForDataSource(), + + "attestation_uri": { + Type: schema.TypeString, + Computed: true, + }, + + "trust_model": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tags.SchemaDataSource(), + }, + } +} + +func dataSourceArmAttestationProviderRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Attestation.ProviderClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Attestation Provider %q (Resource Group %q) was not found", name, resourceGroup) + } + return fmt.Errorf("retrieving Attestation %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.Set("name", name) + d.Set("resource_group_name", resourceGroup) + d.Set("location", location.NormalizeNilable(resp.Location)) + + if props := resp.StatusResult; props != nil { + d.Set("attestation_uri", props.AttestURI) + d.Set("trust_model", props.TrustModel) + } + + if resp.ID == nil || *resp.ID == "" { + return fmt.Errorf("empty or nil ID returned for Attestation Provider %q (Resource Group %q)", name, resourceGroup) + } + d.SetId(*resp.ID) + + return tags.FlattenAndSet(d, resp.Tags) +} diff --git a/azurerm/internal/services/attestation/attestation_provider_resource.go b/azurerm/internal/services/attestation/attestation_provider_resource.go new file mode 100644 index 000000000000..87948c482983 --- /dev/null +++ b/azurerm/internal/services/attestation/attestation_provider_resource.go @@ -0,0 +1,227 @@ +package attestation + +import ( + "encoding/base64" + "encoding/pem" + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/attestation/mgmt/2018-09-01-preview/attestation" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/attestation/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/attestation/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmAttestationProvider() *schema.Resource { + return &schema.Resource{ + Create: resourceArmAttestationProviderCreate, + Read: resourceArmAttestationProviderRead, + Update: resourceArmAttestationProviderUpdate, + Delete: resourceArmAttestationProviderDelete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.AttestationId(id) + return err + }), + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.AttestationName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "policy_signing_certificate_data": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validate.IsCert, + }, + + "tags": tags.Schema(), + + "attestation_uri": { + Type: schema.TypeString, + Computed: true, + }, + + "trust_model": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} +func resourceArmAttestationProviderCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Attestation.ProviderClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing Attestation Provider %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_attestation_provider", *existing.ID) + } + + props := attestation.ServiceCreationParams{ + Location: utils.String(location.Normalize(d.Get("location").(string))), + Properties: &attestation.ServiceCreationSpecificParams{ + // AttestationPolicy was deprecated in October of 2019 + }, + Tags: tags.Expand(d.Get("tags").(map[string]interface{})), + } + + // NOTE: This maybe an slice in a future release or even a slice of slices + // The service team does not currently have any user data for this + // resource. + policySigningCertificate := d.Get("policy_signing_certificate_data").(string) + + if policySigningCertificate != "" { + block, _ := pem.Decode([]byte(policySigningCertificate)) + if block == nil { + return fmt.Errorf("invalid X.509 certificate, unable to decode") + } + + v := base64.StdEncoding.EncodeToString(block.Bytes) + props.Properties.PolicySigningCertificates = expandArmAttestationProviderJSONWebKeySet(v) + } + + if _, err := client.Create(ctx, resourceGroup, name, props); err != nil { + return fmt.Errorf("creating Attestation Provider %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("retrieving Attestation Provider %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + if resp.ID == nil || *resp.ID == "" { + return fmt.Errorf("empty or nil ID returned for Attestation Provider %q (Resource Group %q)", name, resourceGroup) + } + + d.SetId(*resp.ID) + return resourceArmAttestationProviderRead(d, meta) +} + +func resourceArmAttestationProviderRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Attestation.ProviderClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.AttestationId(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] attestation %q does not exist - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("retrieving Attestation Provider %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + d.Set("name", id.Name) + d.Set("resource_group_name", id.ResourceGroup) + d.Set("location", location.NormalizeNilable(resp.Location)) + + if props := resp.StatusResult; props != nil { + d.Set("attestation_uri", props.AttestURI) + d.Set("trust_model", props.TrustModel) + } + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceArmAttestationProviderUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Attestation.ProviderClient + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.AttestationId(d.Id()) + if err != nil { + return err + } + + updateParams := attestation.ServicePatchParams{} + if d.HasChange("tags") { + updateParams.Tags = tags.Expand(d.Get("tags").(map[string]interface{})) + } + + if _, err := client.Update(ctx, id.ResourceGroup, id.Name, updateParams); err != nil { + return fmt.Errorf("updating Attestation Provider %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + return resourceArmAttestationProviderRead(d, meta) +} + +func resourceArmAttestationProviderDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Attestation.ProviderClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.AttestationId(d.Id()) + if err != nil { + return err + } + + if _, err := client.Delete(ctx, id.ResourceGroup, id.Name); err != nil { + return fmt.Errorf("deleting Attestation Provider %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + return nil +} + +func expandArmAttestationProviderJSONWebKeySet(pem string) *attestation.JSONWebKeySet { + if len(pem) == 0 { + return nil + } + + result := attestation.JSONWebKeySet{ + Keys: expandArmAttestationProviderJSONWebKeyArray(pem), + } + + return &result +} + +func expandArmAttestationProviderJSONWebKeyArray(pem string) *[]attestation.JSONWebKey { + results := make([]attestation.JSONWebKey, 0) + certs := []string{pem} + + result := attestation.JSONWebKey{ + Kty: utils.String("RSA"), + X5c: &certs, + } + + results = append(results, result) + + return &results +} diff --git a/azurerm/internal/services/attestation/client/client.go b/azurerm/internal/services/attestation/client/client.go new file mode 100644 index 000000000000..a54f53e5e1c5 --- /dev/null +++ b/azurerm/internal/services/attestation/client/client.go @@ -0,0 +1,19 @@ +package client + +import ( + "github.com/Azure/azure-sdk-for-go/services/preview/attestation/mgmt/2018-09-01-preview/attestation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common" +) + +type Client struct { + ProviderClient *attestation.ProvidersClient +} + +func NewClient(o *common.ClientOptions) *Client { + providerClient := attestation.NewProvidersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&providerClient.Client, o.ResourceManagerAuthorizer) + + return &Client{ + ProviderClient: &providerClient, + } +} diff --git a/azurerm/internal/services/attestation/parse/attestation.go b/azurerm/internal/services/attestation/parse/attestation.go new file mode 100644 index 000000000000..534d5ca82da1 --- /dev/null +++ b/azurerm/internal/services/attestation/parse/attestation.go @@ -0,0 +1,31 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type AttestationProviderId struct { + ResourceGroup string + Name string +} + +func AttestationId(input string) (*AttestationProviderId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("parsing attestation ID %q: %+v", input, err) + } + + attestationProvider := AttestationProviderId{ + ResourceGroup: id.ResourceGroup, + } + if attestationProvider.Name, err = id.PopSegment("attestationProviders"); err != nil { + return nil, err + } + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &attestationProvider, nil +} diff --git a/azurerm/internal/services/attestation/parse/attestation_test.go b/azurerm/internal/services/attestation/parse/attestation_test.go new file mode 100644 index 000000000000..69c5756ea4d9 --- /dev/null +++ b/azurerm/internal/services/attestation/parse/attestation_test.go @@ -0,0 +1,72 @@ +package parse + +import ( + "testing" +) + +func TestAttestationProviderID(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *AttestationProviderId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Expected: nil, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Expected: nil, + }, + { + Name: "Missing AttestationProvider Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Attestation/attestationProviders", + Expected: nil, + }, + { + Name: "attestation Provider ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Attestation/attestationProviders/provider1", + Expected: &AttestationProviderId{ + ResourceGroup: "resourceGroup1", + Name: "provider1", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Attestation/AttestationProviders/provider1", + Expected: nil, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q..", v.Name) + + actual, err := AttestationId(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/attestation/registration.go b/azurerm/internal/services/attestation/registration.go new file mode 100644 index 000000000000..02ec076ea35a --- /dev/null +++ b/azurerm/internal/services/attestation/registration.go @@ -0,0 +1,31 @@ +package attestation + +import "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + +type Registration struct{} + +// Name is the name of this Service +func (r Registration) Name() string { + return "Attestation" +} + +// WebsiteCategories returns a list of categories which can be used for the sidebar +func (r Registration) WebsiteCategories() []string { + return []string{ + "Attestation", + } +} + +// SupportedDataSources returns the supported Data Sources supported by this Service +func (r Registration) SupportedDataSources() map[string]*schema.Resource { + return map[string]*schema.Resource{ + "azurerm_attestation_provider": dataSourceAttestationProvider(), + } +} + +// SupportedResources returns the supported Resources supported by this Service +func (r Registration) SupportedResources() map[string]*schema.Resource { + return map[string]*schema.Resource{ + "azurerm_attestation_provider": resourceArmAttestationProvider(), + } +} diff --git a/azurerm/internal/services/attestation/tests/attestation_data_source_test.go b/azurerm/internal/services/attestation/tests/attestation_data_source_test.go new file mode 100644 index 000000000000..1a96f88cc3e3 --- /dev/null +++ b/azurerm/internal/services/attestation/tests/attestation_data_source_test.go @@ -0,0 +1,42 @@ +package tests + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" +) + +func TestAccDataSourceAzureRMAttestationProvider_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_attestation_provider", "test") + randStr := strings.ToLower(acctest.RandString(10)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMAttestationProviderDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAttestationProvider_basic(data, randStr), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAttestationProviderExists(data.ResourceName), + ), + }, + }, + }) +} + +func testAccDataSourceAttestationProvider_basic(data acceptance.TestData, randStr string) string { + config := testAccAzureRMAttestationProvider_basic(data, randStr) + return fmt.Sprintf(` +%s + +data "azurerm_attestation_provider" "test" { + name = azurerm_attestation_provider.test.name + resource_group_name = azurerm_attestation_provider.test.resource_group_name +} +`, config) +} diff --git a/azurerm/internal/services/attestation/tests/attestation_resource_test.go b/azurerm/internal/services/attestation/tests/attestation_resource_test.go new file mode 100644 index 000000000000..371a62806e1d --- /dev/null +++ b/azurerm/internal/services/attestation/tests/attestation_resource_test.go @@ -0,0 +1,320 @@ +package tests + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/attestation/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMAttestationProvider_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_attestation_provider", "test") + randStr := strings.ToLower(acctest.RandString(10)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMAttestationProviderDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAttestationProvider_basic(data, randStr), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAttestationProviderExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMAttestationProvider_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_attestation_provider", "test") + randStr := strings.ToLower(acctest.RandString(10)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMAttestationProviderDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAttestationProvider_basic(data, randStr), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAttestationProviderExists(data.ResourceName), + ), + }, + data.RequiresImportErrorStep(testAccAzureRMAttestationProvider_requiresImport), + }, + }) +} + +func TestAccAzureRMAttestationProvider_completeString(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_attestation_provider", "test") + randStr := strings.ToLower(acctest.RandString(10)) + testCertificate, err := testAzureRMGenerateTestCertificate("ENCOM") + if err != nil { + t.Fatalf("Test case failed: '%+v'", err) + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMAttestationProviderDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAttestationProvider_completeString(data, randStr, testCertificate), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAttestationProviderExists(data.ResourceName), + ), + }, + // must ignore policy_signing_certificate since the API does not return these values + data.ImportStep("policy_signing_certificate"), + }, + }) +} + +func TestAccAzureRMAttestationProvider_completeFile(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_attestation_provider", "test") + randStr := strings.ToLower(acctest.RandString(10)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMAttestationProviderDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAttestationProvider_completeFile(data, randStr), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAttestationProviderExists(data.ResourceName), + ), + }, + // must ignore policy_signing_certificate since the API does not return these values + data.ImportStep("policy_signing_certificate"), + }, + }) +} + +func TestAccAzureRMAttestationProvider_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_attestation_provider", "test") + randStr := strings.ToLower(acctest.RandString(10)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMAttestationProviderDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMAttestationProvider_basic(data, randStr), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAttestationProviderExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMAttestationProvider_update(data, randStr), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAttestationProviderExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMAttestationProvider_basic(data, randStr), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAttestationProviderExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testCheckAzureRMAttestationProviderExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Attestation.ProviderClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("attestation AttestationProvider not found: %s", resourceName) + } + id, err := parse.AttestationId(rs.Primary.ID) + if err != nil { + return err + } + if resp, err := client.Get(ctx, id.ResourceGroup, id.Name); err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("bad: Attestation Provider %q does not exist", id.Name) + } + return fmt.Errorf("bad: Get on Attestation.ProviderClient: %+v", err) + } + return nil + } +} + +func testAzureRMGenerateTestCertificate(organization string) (string, error) { + privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + return "", err + } + + rawCert := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{organization}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 180), + + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + certBytes, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &privateKey.PublicKey, privateKey) + if err != nil { + return "", fmt.Errorf("unable to create test certificate: %+v", err) + } + + encoded := &bytes.Buffer{} + if err := pem.Encode(encoded, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}); err != nil { + return "", fmt.Errorf("unable to pem encode test certificate: %+v", err) + } + + return encoded.String(), nil +} + +func testCheckAzureRMAttestationProviderDestroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Attestation.ProviderClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_attestation_provider" { + continue + } + id, err := parse.AttestationId(rs.Primary.ID) + if err != nil { + return err + } + if resp, err := client.Get(ctx, id.ResourceGroup, id.Name); err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("bad: Get on Attestation.ProviderClient: %+v", err) + } + } + return nil + } + return nil +} + +// currently only supported in "East US 2", "West Central US" & "UK South" +func testAccAzureRMAttestationProvider_template(data acceptance.TestData) string { + return fmt.Sprintf(` +// TODO: switch to using regular regions when this is supported +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-attestation-%d" + location = "%s" +} +`, data.RandomInteger, "uksouth") +} + +func testAccAzureRMAttestationProvider_basic(data acceptance.TestData, randStr string) string { + template := testAccAzureRMAttestationProvider_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 +} +`, template, randStr) +} + +func testAccAzureRMAttestationProvider_update(data acceptance.TestData, randStr string) string { + template := testAccAzureRMAttestationProvider_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 + + tags = { + ENV = "Test" + } +} +`, template, randStr) +} + +func testAccAzureRMAttestationProvider_requiresImport(data acceptance.TestData) string { + randStr := strings.ToLower(acctest.RandString(10)) + config := testAccAzureRMAttestationProvider_basic(data, randStr) + + return fmt.Sprintf(` +%s + +resource "azurerm_attestation_provider" "import" { + name = azurerm_attestation_provider.test.name + resource_group_name = azurerm_attestation_provider.test.resource_group_name + location = azurerm_attestation_provider.test.location +} +`, config) +} + +func testAccAzureRMAttestationProvider_completeString(data acceptance.TestData, randStr string, testCertificate string) string { + template := testAccAzureRMAttestationProvider_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_signing_certificate_data = <azurerm_automation_variable_string +
  • + azurerm_attestation +
  • +
  • azurerm_availability_set
  • @@ -1760,6 +1764,15 @@ +
  • + Attestation Resources + +
  • +
  • IoTCentral Application Resources