diff --git a/go.mod b/go.mod index ba4f68e99d..e19cbce92c 100644 --- a/go.mod +++ b/go.mod @@ -77,3 +77,5 @@ require ( ) go 1.17 + +replace github.com/manicminer/hamilton => github.com/o11n/hamilton v0.40.2-0.20220217143703-8395e584f1c4 diff --git a/go.sum b/go.sum index c2ca6a4d5f..06994aeb62 100644 --- a/go.sum +++ b/go.sum @@ -299,8 +299,6 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/manicminer/hamilton v0.41.1 h1:b9XVMIo2tnHBtl7sFTmake2BddbqriW2zdPKWmrxZsc= -github.com/manicminer/hamilton v0.41.1/go.mod h1:IOYn2Dc9SUiZ7Ryw6c8Ay795vPPMnrCZe3MktS447dc= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -338,6 +336,8 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= +github.com/o11n/hamilton v0.40.2-0.20220217143703-8395e584f1c4 h1:ai0wzt2ne+aHdBZ6ibAKODjMf6FTwgbvpxf2sp4kW3c= +github.com/o11n/hamilton v0.40.2-0.20220217143703-8395e584f1c4/go.mod h1:IOYn2Dc9SUiZ7Ryw6c8Ay795vPPMnrCZe3MktS447dc= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= diff --git a/internal/services/serviceprincipals/service_principal_data_source_test.go b/internal/services/serviceprincipals/service_principal_data_source_test.go index 5b9b4e55f3..325572d939 100644 --- a/internal/services/serviceprincipals/service_principal_data_source_test.go +++ b/internal/services/serviceprincipals/service_principal_data_source_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "testing" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -85,31 +86,34 @@ func (ServicePrincipalDataSource) testCheckFunc(data acceptance.TestData) resour } func (ServicePrincipalDataSource) byApplicationId(data acceptance.TestData) string { + endDate := time.Now().AddDate(0, 3, 27).UTC().Format(time.RFC3339) return fmt.Sprintf(` %[1]s data "azuread_service_principal" "test" { application_id = azuread_service_principal.test.application_id } -`, ServicePrincipalResource{}.complete(data)) +`, ServicePrincipalResource{}.complete(data, endDate)) } func (ServicePrincipalDataSource) byDisplayName(data acceptance.TestData) string { + endDate := time.Now().AddDate(0, 3, 27).UTC().Format(time.RFC3339) return fmt.Sprintf(` %[1]s data "azuread_service_principal" "test" { display_name = azuread_service_principal.test.display_name } -`, ServicePrincipalResource{}.complete(data)) +`, ServicePrincipalResource{}.complete(data, endDate)) } func (ServicePrincipalDataSource) byObjectId(data acceptance.TestData) string { + endDate := time.Now().AddDate(0, 3, 27).UTC().Format(time.RFC3339) return fmt.Sprintf(` %[1]s data "azuread_service_principal" "test" { object_id = azuread_service_principal.test.object_id } -`, ServicePrincipalResource{}.complete(data)) +`, ServicePrincipalResource{}.complete(data, endDate)) } diff --git a/internal/services/serviceprincipals/service_principal_resource.go b/internal/services/serviceprincipals/service_principal_resource.go index ed3f8f9808..c02c11ce48 100644 --- a/internal/services/serviceprincipals/service_principal_resource.go +++ b/internal/services/serviceprincipals/service_principal_resource.go @@ -317,6 +317,29 @@ func servicePrincipalResource() *schema.Resource { }, }, + "token_signing_certificate_name": { + Description: "Name of a token signing certificate created for the service principal. If set 'token_signing_certificate_end_date' must be set as well.", + Type: schema.TypeString, + Optional: true, + RequiredWith: []string{"token_signing_certificate_end_date"}, + ForceNew: true, + }, + + "token_signing_certificate_end_date": { + Description: "The start date from which the certificate is valid, formatted as an RFC3339 date string (e.g. `2018-01-01T01:02:03Z`). If set 'token_signing_certificate_name' must be set as well.", + Type: schema.TypeString, + Optional: true, + RequiredWith: []string{"token_signing_certificate_name"}, + ForceNew: true, + ValidateFunc: validation.IsRFC3339Time, + }, + + "preferred_token_signing_key_thumbprint": { + Description: "The preferred token signing thumbprint of the principal. Will be filled automatically when a token signing certificate is getting created", + Type: schema.TypeString, + Computed: true, + }, + "sign_in_audience": { Description: "The Microsoft account types that are supported for the associated application", Type: schema.TypeString, @@ -502,6 +525,27 @@ func servicePrincipalResourceCreate(ctx context.Context, d *schema.ResourceData, } } + if v, ok := d.GetOk("token_signing_certificate_name"); ok { + var endDate time.Time + if v, ok := d.GetOk("token_signing_certificate_end_date"); ok { + endDate, err = time.Parse(time.RFC3339, v.(string)) + if err != nil { + tf.ErrorDiagF(err, "Unable to parse the provided token_signing_certificate_end_date %q: %+v", v, err) + } + } + key, _, err := client.AddTokenSigningCertificate(ctx, d.Id(), msgraph.KeyCredential{ + DisplayName: utils.String(v.(string)), + EndDateTime: &endDate, + }) + if err != nil { + return tf.ErrorDiagF(err, "Could not add token signing certificate to service principal with object ID: %q", d.Id()) + } + + if _, err = client.SetPreferredTokenSigningKeyThumbprint(ctx, d.Id(), *key.Thumbprint); err != nil { + return tf.ErrorDiagF(err, "Could not set preferred token signing key thumbprint for service principal with object ID: %q", d.Id()) + } + } + return servicePrincipalResourceRead(ctx, d, meta) } @@ -618,6 +662,7 @@ func servicePrincipalResourceRead(ctx context.Context, d *schema.ResourceData, m tf.Set(d, "oauth2_permission_scope_ids", helpers.ApplicationFlattenOAuth2PermissionScopeIDs(servicePrincipal.PublishedPermissionScopes)) tf.Set(d, "oauth2_permission_scopes", helpers.ApplicationFlattenOAuth2PermissionScopes(servicePrincipal.PublishedPermissionScopes)) tf.Set(d, "object_id", servicePrincipal.ID) + tf.Set(d, "preferred_token_signing_key_thumbprint", servicePrincipal.PreferredTokenSigningKeyThumbprint) tf.Set(d, "preferred_single_sign_on_mode", servicePrincipal.PreferredSingleSignOnMode) tf.Set(d, "redirect_uris", tf.FlattenStringSlicePtr(servicePrincipal.ReplyUrls)) tf.Set(d, "saml_metadata_url", servicePrincipal.SamlMetadataUrl) diff --git a/internal/services/serviceprincipals/service_principal_resource_test.go b/internal/services/serviceprincipals/service_principal_resource_test.go index c99f0921ac..dccfa9ca04 100644 --- a/internal/services/serviceprincipals/service_principal_resource_test.go +++ b/internal/services/serviceprincipals/service_principal_resource_test.go @@ -5,7 +5,9 @@ import ( "fmt" "net/http" "os" + "regexp" "testing" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -32,7 +34,7 @@ func TestAccServicePrincipal_basic(t *testing.T) { check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), }) } @@ -40,10 +42,11 @@ func TestAccServicePrincipal_complete(t *testing.T) { data := acceptance.BuildTestData(t, "azuread_service_principal", "test") r := ServicePrincipalResource{} tenantId := os.Getenv("ARM_TENANT_ID") + endDate := time.Now().AddDate(0, 3, 27).UTC().Format(time.RFC3339) data.ResourceTest(t, r, []resource.TestStep{ { - Config: r.complete(data), + Config: r.complete(data, endDate), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("app_role_ids.%").HasValue("2"), @@ -57,15 +60,17 @@ func TestAccServicePrincipal_complete(t *testing.T) { check.That(data.ResourceName).Key("redirect_uris.#").HasValue("2"), check.That(data.ResourceName).Key("sign_in_audience").HasValue("AzureADMyOrg"), check.That(data.ResourceName).Key("type").HasValue("Application"), + check.That(data.ResourceName).Key("preferred_token_signing_key_thumbprint").MatchesRegex(regexp.MustCompile("^[A-Z0-9]{40}$")), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), }) } func TestAccServicePrincipal_completeUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azuread_service_principal", "test") r := ServicePrincipalResource{} + endDate := time.Now().AddDate(0, 3, 27).UTC().Format(time.RFC3339) data.ResourceTest(t, r, []resource.TestStep{ { @@ -78,9 +83,9 @@ func TestAccServicePrincipal_completeUpdate(t *testing.T) { check.That(data.ResourceName).Key("oauth2_permission_scopes.#").HasValue("0"), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), { - Config: r.complete(data), + Config: r.complete(data, endDate), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("app_roles.#").HasValue("2"), @@ -89,7 +94,7 @@ func TestAccServicePrincipal_completeUpdate(t *testing.T) { check.That(data.ResourceName).Key("oauth2_permission_scopes.#").HasValue("2"), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), { Config: r.basic(data), Check: resource.ComposeTestCheckFunc( @@ -100,7 +105,7 @@ func TestAccServicePrincipal_completeUpdate(t *testing.T) { check.That(data.ResourceName).Key("oauth2_permission_scopes.#").HasValue("0"), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), }) } @@ -115,13 +120,14 @@ func TestAccServicePrincipal_featureTags(t *testing.T) { check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), }) } func TestAccServicePrincipal_featureTagsUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azuread_service_principal", "test") r := ServicePrincipalResource{} + endDate := time.Now().AddDate(0, 3, 27).UTC().Format(time.RFC3339) data.ResourceTest(t, r, []resource.TestStep{ { @@ -130,63 +136,63 @@ func TestAccServicePrincipal_featureTagsUpdate(t *testing.T) { check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), { Config: r.basic(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), { Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), { Config: r.basic(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), { Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), { - Config: r.complete(data), + Config: r.complete(data, endDate), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), { Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), { Config: r.noFeatureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), { Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), }) } @@ -293,7 +299,7 @@ func TestAccServicePrincipal_useExisting(t *testing.T) { check.That(data.ResourceName).Key("oauth2_permission_scopes.#").Exists(), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), }) } @@ -308,7 +314,7 @@ func TestAccServicePrincipal_fromApplicationTemplate(t *testing.T) { check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep("use_existing"), + data.ImportStep("token_signing_certificate_name", "token_signing_certificate_end_date", "use_existing"), }) } @@ -408,20 +414,22 @@ resource "azuread_application" "test" { `, data.RandomInteger, data.UUID(), data.UUID(), data.UUID(), data.UUID()) } -func (r ServicePrincipalResource) complete(data acceptance.TestData) string { +func (r ServicePrincipalResource) complete(data acceptance.TestData, endDate string) string { return fmt.Sprintf(` %[1]s resource "azuread_service_principal" "test" { application_id = azuread_application.test.application_id - account_enabled = false - alternative_names = ["foo", "bar"] - app_role_assignment_required = true - description = "An internal app for testing" - login_url = "https://test-%[2]d.internal/login" - notes = "Just testing something" - preferred_single_sign_on_mode = "saml" + account_enabled = false + alternative_names = ["foo", "bar"] + app_role_assignment_required = true + description = "An internal app for testing" + login_url = "https://test-%[2]d.internal/login" + notes = "Just testing something" + preferred_single_sign_on_mode = "saml" + token_signing_certificate_name = "testcert" + token_signing_certificate_end_date = "%[3]s" notification_email_addresses = [ "alerts.internal@hashitown.net", @@ -439,7 +447,7 @@ resource "azuread_service_principal" "test" { "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1", ] } -`, r.templateComplete(data), data.RandomInteger) +`, r.templateComplete(data), data.RandomInteger, endDate) } func (r ServicePrincipalResource) featureTags(data acceptance.TestData) string { diff --git a/vendor/github.com/manicminer/hamilton/msgraph/models.go b/vendor/github.com/manicminer/hamilton/msgraph/models.go index 95d83333e9..57a5cf866c 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/models.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/models.go @@ -1044,6 +1044,7 @@ type KeyCredential struct { EndDateTime *time.Time `json:"endDateTime,omitempty"` KeyId *string `json:"keyId,omitempty"` StartDateTime *time.Time `json:"startDateTime,omitempty"` + Thumbprint *string `json:"thumbprint,omitempty"` Type KeyCredentialType `json:"type"` Usage KeyCredentialUsage `json:"usage"` Key *string `json:"key,omitempty"` @@ -1274,6 +1275,7 @@ type ServicePrincipal struct { PasswordCredentials *[]PasswordCredential `json:"passwordCredentials,omitempty"` PasswordSingleSignOnSettings *PasswordSingleSignOnSettings `json:"passwordSingleSignOnSettings,omitempty"` PreferredSingleSignOnMode *PreferredSingleSignOnMode `json:"preferredSingleSignOnMode,omitempty"` + PreferredTokenSigningKeyThumbprint *string `json:"preferredTokenSigningKeyThumbprint,omitempty"` PreferredTokenSigningKeyEndDateTime *time.Time `json:"preferredTokenSigningKeyEndDateTime,omitempty"` PublishedPermissionScopes *[]PermissionScope `json:"publishedPermissionScopes,omitempty"` ReplyUrls *[]string `json:"replyUrls,omitempty"` diff --git a/vendor/github.com/manicminer/hamilton/msgraph/serviceprincipals.go b/vendor/github.com/manicminer/hamilton/msgraph/serviceprincipals.go index 45fc367e64..d90abfc19b 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/serviceprincipals.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/serviceprincipals.go @@ -449,6 +449,75 @@ func (c *ServicePrincipalsClient) RemovePassword(ctx context.Context, servicePri return status, nil } +// AddTokenSigningCertificate appends a new self signed certificate (keys and password) to a Service Principal. +func (c *ServicePrincipalsClient) AddTokenSigningCertificate(ctx context.Context, servicePrincipalId string, keyCredential KeyCredential) (*KeyCredential, int, error) { + var status int + + body, err := json.Marshal(struct { + KeyCredential KeyCredential `json:"keyCredential"` + }{ + KeyCredential: keyCredential, + }) + if err != nil { + return nil, status, fmt.Errorf("json.Marshal(): %v", err) + } + + resp, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + Body: body, + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ValidStatusCodes: []int{http.StatusOK, http.StatusCreated}, + Uri: Uri{ + Entity: fmt.Sprintf("/servicePrincipals/%s/addTokenSigningCertificate", servicePrincipalId), + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("ServicePrincipalsClient.BaseClient.Post(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var newKeyCredential KeyCredential + if err := json.Unmarshal(respBody, &newKeyCredential); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &newKeyCredential, status, nil +} + +// SetPreferredTokenSigningKeyThumbprint sets the field preferredTokenSigningKeyThumbprint for a Service Principal. +func (c *ServicePrincipalsClient) SetPreferredTokenSigningKeyThumbprint(ctx context.Context, servicePrincipalId string, thumbprint string) (int, error) { + var status int + + body, err := json.Marshal(struct { + Thumbprint string `json:"preferredTokenSigningKeyThumbprint"` + }{ + Thumbprint: thumbprint, + }) + if err != nil { + return status, fmt.Errorf("json.Marshal(): %v", err) + } + + _, status, _, err = c.BaseClient.Patch(ctx, PatchHttpRequestInput{ + Body: body, + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + ValidStatusCodes: []int{http.StatusNoContent}, + Uri: Uri{ + Entity: fmt.Sprintf("/servicePrincipals/%s", servicePrincipalId), + HasTenantId: true, + }, + }) + if err != nil { + return status, fmt.Errorf("ServicePrincipalsClient.BaseClient.Patch(): %v", err) + } + + return status, nil +} + // ListOwnedObjects retrieves the owned objects of the specified Service Principal. // id is the object ID of the service principal. func (c *ServicePrincipalsClient) ListOwnedObjects(ctx context.Context, id string) (*[]string, int, error) { diff --git a/vendor/modules.txt b/vendor/modules.txt index 3748d69d79..90e61a73d8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -229,7 +229,7 @@ github.com/klauspost/compress/fse github.com/klauspost/compress/huff0 github.com/klauspost/compress/zstd github.com/klauspost/compress/zstd/internal/xxhash -# github.com/manicminer/hamilton v0.41.1 +# github.com/manicminer/hamilton v0.41.1 => github.com/o11n/hamilton v0.40.2-0.20220217143703-8395e584f1c4 ## explicit; go 1.16 github.com/manicminer/hamilton/auth github.com/manicminer/hamilton/environments @@ -507,3 +507,4 @@ google.golang.org/protobuf/types/known/durationpb google.golang.org/protobuf/types/known/emptypb google.golang.org/protobuf/types/known/timestamppb google.golang.org/protobuf/types/pluginpb +# github.com/manicminer/hamilton => github.com/o11n/hamilton v0.40.2-0.20220217143703-8395e584f1c4