-
Notifications
You must be signed in to change notification settings - Fork 337
New Resource: azuread_flexible_federated_identity_credential
#1788
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
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
9205f93
update go-azure-sdk to vv0.20251029.1173336
jackofallops 88cc109
go mod tidy
jackofallops ecd5274
add new beta resoruce application flexible FIC
jackofallops fb1553a
terrafmt
jackofallops 8ae7f0b
re-vendor beta api after rebase
jackofallops 889793d
fixup docs example
jackofallops b06f002
Refactored to TypedSDK
jackofallops acd12a1
add custom poller for create
jackofallops 3daf39a
address review feedback
jackofallops 8f1495d
fmt example in docs
jackofallops File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
70 changes: 70 additions & 0 deletions
70
docs/resources/application_flexible_federated_identity_credential.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| --- | ||
| subcategory: "Applications" | ||
| --- | ||
|
|
||
| # Resource: azuread_application_flexible_federated_identity_credential | ||
|
|
||
| Manages a flexible federated identity credential associated with an application within Azure Active Directory. | ||
|
|
||
| ## API Permissions | ||
|
|
||
| The following API permissions are required in order to use this resource. | ||
|
|
||
| When authenticated with a service principal, this resource requires one of the following application roles: `Application.ReadWrite.OwnedBy` or `Application.ReadWrite.All` | ||
|
|
||
| -> When using the `Application.ReadWrite.OwnedBy` application role, the principal being used to run Terraform must be an owner of the application. | ||
|
|
||
| When authenticated with a user principal, this resource requires one of the following directory roles: `Application Administrator` or `Global Administrator` | ||
|
|
||
| ## Example Usage | ||
|
|
||
| ```terraform | ||
| resource "azuread_application_registration" "example" { | ||
| display_name = "example" | ||
| } | ||
|
|
||
| resource "azuread_application_flexible_federated_identity_credential" "example" { | ||
| application_id = azuread_application_registration.example.id | ||
| claims_matching_expression = "claims['sub'] matches 'repo:contoso/contoso-repo:ref:refs/heads/*' and claims['job_workflow_ref'] matches 'contoso/contoso-prod/.github/workflows/*.yml@refs/heads/main'" | ||
| display_name = "my-repo-deploy" | ||
| description = "Deployments for my-repo" | ||
| audience = "api://AzureADTokenExchange" | ||
| issuer = "https://token.actions.githubusercontent.com" | ||
| } | ||
| ``` | ||
|
|
||
| ## Argument Reference | ||
|
|
||
| The following arguments are supported: | ||
|
|
||
| * `application_id` - (Required) The resource ID of the application for which this federated identity credential should be created. Changing this field forces a new resource to be created. | ||
| * `audience` - (Required) The audience that can appear in the external token. This specifies what should be accepted in the `aud` claim of incoming tokens. | ||
| * `claims_matching_expression` - (Required) The expression to match for claims. See the [Preview Documentation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identities-flexible-federated-identity-credentials?tabs=terraformcloud#flexible-federated-identity-credential-expression-language-functionality) for more information. | ||
| * `description` - (Optional) A description for the federated identity credential. | ||
| * `display_name` - (Required) A unique display name for the federated identity credential. Changing this forces a new resource to be created. | ||
| * `issuer` - (Required) The URL of the external identity provider, which must match the issuer claim of the external token being exchanged. | ||
|
|
||
| ## Attributes Reference | ||
|
|
||
| In addition to all arguments above, the following attributes are exported: | ||
|
|
||
| * `credential_id` - A UUID used to uniquely identify this federated identity credential. | ||
|
|
||
| ## Timeouts | ||
|
|
||
| The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: | ||
|
|
||
| * `create` - (Defaults to 15 minutes) Used when creating the resource. | ||
| * `read` - (Defaults to 5 minutes) Used when retrieving the resource. | ||
| * `update` - (Defaults to 5 minutes) Used when updating the resource. | ||
| * `delete` - (Defaults to 5 minutes) Used when deleting the resource. | ||
|
|
||
| ## Import | ||
|
|
||
| Flexible Federated Identity Credentials can be imported using the object ID of the associated application and the ID of the flexible federated identity credential, e.g. | ||
|
|
||
| ```shell | ||
| terraform import azuread_application_flexible_federated_identity_credential.example 00000000-0000-0000-0000-000000000000/federatedIdentityCredential/11111111-1111-1111-1111-111111111111 | ||
| ``` | ||
|
|
||
| -> This ID format is unique to Terraform and is composed of the application's object ID, the string "federatedIdentityCredential" and the credential ID in the format `{ObjectId}/federatedIdentityCredential/{CredentialId}`. | ||
305 changes: 305 additions & 0 deletions
305
...rnal/services/applications/application_flexible_federated_identity_credential_resource.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,305 @@ | ||
| // Copyright (c) HashiCorp, Inc. | ||
| // SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| package applications | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "time" | ||
|
|
||
| "github.com/hashicorp/go-azure-helpers/lang/pointer" | ||
| "github.com/hashicorp/go-azure-helpers/lang/response" | ||
| "github.com/hashicorp/go-azure-sdk/microsoft-graph/applications/beta/application" | ||
| "github.com/hashicorp/go-azure-sdk/microsoft-graph/applications/beta/federatedidentitycredential" | ||
| "github.com/hashicorp/go-azure-sdk/microsoft-graph/common-types/beta" | ||
| "github.com/hashicorp/go-azure-sdk/microsoft-graph/common-types/stable" | ||
| "github.com/hashicorp/go-azure-sdk/sdk/client/pollers" | ||
| "github.com/hashicorp/go-azure-sdk/sdk/nullable" | ||
| "github.com/hashicorp/terraform-provider-azuread/internal/helpers/consistency" | ||
| "github.com/hashicorp/terraform-provider-azuread/internal/helpers/tf" | ||
| "github.com/hashicorp/terraform-provider-azuread/internal/helpers/tf/pluginsdk" | ||
| "github.com/hashicorp/terraform-provider-azuread/internal/helpers/tf/validation" | ||
| "github.com/hashicorp/terraform-provider-azuread/internal/sdk" | ||
| "github.com/hashicorp/terraform-provider-azuread/internal/services/applications/custompollers" | ||
| ) | ||
|
|
||
| type flexibleFederatedIdentityCredentialResource struct{} | ||
|
|
||
| var _ sdk.ResourceWithUpdate = &flexibleFederatedIdentityCredentialResource{} | ||
|
|
||
| type flexibleFederatedIdentityCredentialModel struct { | ||
| ApplicationId string `tfschema:"application_id"` | ||
| Audience string `tfschema:"audience"` | ||
| ClaimsMatchingExpression string `tfschema:"claims_matching_expression"` | ||
| Description string `tfschema:"description"` | ||
| DisplayName string `tfschema:"display_name"` | ||
| Issuer string `tfschema:"issuer"` | ||
| CredentialId string `tfschema:"credential_id"` | ||
| } | ||
|
|
||
| func (f flexibleFederatedIdentityCredentialResource) Arguments() map[string]*pluginsdk.Schema { | ||
| return map[string]*pluginsdk.Schema{ | ||
| "application_id": { | ||
| Description: "The resource ID of the application for which this flexible federated identity credential should be created", | ||
| Type: pluginsdk.TypeString, | ||
| Required: true, | ||
| ForceNew: true, | ||
| ValidateFunc: stable.ValidateApplicationID, | ||
| }, | ||
|
|
||
| "audience": { | ||
| Description: "The audience that can appear in the external token. This specifies what should be accepted in the `aud` claim of incoming tokens.", | ||
| Type: pluginsdk.TypeString, | ||
| Required: true, | ||
| ValidateFunc: validation.StringLenBetween(1, 600), | ||
| }, | ||
|
|
||
| "claims_matching_expression": { | ||
| Type: pluginsdk.TypeString, | ||
| Required: true, | ||
| ValidateFunc: validation.StringIsNotWhiteSpace, | ||
| Description: "The expression to match for claims.", | ||
| }, | ||
|
|
||
| "display_name": { | ||
| Description: "A unique display name for the flexible federated identity credential", | ||
| Type: pluginsdk.TypeString, | ||
| Required: true, | ||
| ForceNew: true, | ||
| ValidateFunc: validation.StringLenBetween(1, 120), | ||
| }, | ||
|
|
||
| "issuer": { | ||
| Description: "The URL of the external identity provider, which must match the issuer claim of the external token being exchanged. The combination of the values of issuer and subject must be unique on the app.", | ||
| Type: pluginsdk.TypeString, | ||
| Required: true, | ||
| ValidateFunc: validation.StringIsNotWhiteSpace, | ||
| }, | ||
|
|
||
| "description": { | ||
| Description: "A description for the flexible federated identity credential", | ||
| Type: pluginsdk.TypeString, | ||
| Optional: true, | ||
| ValidateFunc: validation.StringLenBetween(0, 600), | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func (f flexibleFederatedIdentityCredentialResource) Attributes() map[string]*pluginsdk.Schema { | ||
| return map[string]*pluginsdk.Schema{ | ||
| "credential_id": { | ||
| Description: "A UUID used to uniquely identify this flexible federated identity credential", | ||
| Type: pluginsdk.TypeString, | ||
| Computed: true, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func (f flexibleFederatedIdentityCredentialResource) ModelObject() interface{} { | ||
| return &flexibleFederatedIdentityCredentialModel{} | ||
| } | ||
|
|
||
| func (f flexibleFederatedIdentityCredentialResource) ResourceType() string { | ||
| return "azuread_application_flexible_federated_identity_credential" | ||
| } | ||
|
|
||
| func (f flexibleFederatedIdentityCredentialResource) Create() sdk.ResourceFunc { | ||
| return sdk.ResourceFunc{ | ||
| Timeout: 15 * time.Minute, | ||
| Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { | ||
| client := metadata.Client.Applications.ApplicationClientBeta | ||
| flexibleFederatedIdentityCredentialClient := metadata.Client.Applications.ApplicationFlexibleFederatedIdentityCredential | ||
|
|
||
| data := &flexibleFederatedIdentityCredentialModel{} | ||
|
|
||
| if err := metadata.Decode(data); err != nil { | ||
| return fmt.Errorf("decoding: %+v", err) | ||
| } | ||
|
|
||
| applicationId, err := beta.ParseApplicationID(data.ApplicationId) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
catriona-m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| tf.LockByName(applicationResourceName, applicationId.ApplicationId) | ||
| defer tf.UnlockByName(applicationResourceName, applicationId.ApplicationId) | ||
|
|
||
| resp, err := client.GetApplication(ctx, *applicationId, application.DefaultGetApplicationOperationOptions()) | ||
| if err != nil { | ||
| return fmt.Errorf("retrieving %s: %+v", applicationId, err) | ||
| } | ||
|
|
||
| app := resp.Model | ||
| if app == nil { | ||
| return fmt.Errorf("retrieving %s: model was nil", applicationId) | ||
| } | ||
|
|
||
| credential := beta.FederatedIdentityCredential{ | ||
| Audiences: []string{data.Audience}, | ||
| ClaimsMatchingExpression: &beta.FederatedIdentityExpression{ | ||
| Value: data.ClaimsMatchingExpression, | ||
| LanguageVersion: 1, // Note - from docs: the language version to be used. Should always be set to 1, and is required to be set/sent. | ||
| }, | ||
| Description: nullable.Value(data.Description), | ||
| Issuer: data.Issuer, | ||
| Name: data.DisplayName, | ||
| } | ||
|
|
||
| federatedIdentityCredentialResp, err := flexibleFederatedIdentityCredentialClient.CreateFederatedIdentityCredential(ctx, *applicationId, credential, federatedidentitycredential.DefaultCreateFederatedIdentityCredentialOperationOptions()) | ||
| if err != nil { | ||
| return fmt.Errorf("adding flexible federated identity credential for %s", applicationId) | ||
| } | ||
|
|
||
| newCredential := federatedIdentityCredentialResp.Model | ||
| if newCredential == nil { | ||
| return fmt.Errorf("api error adding flexible federated identity credential for %s. nil credential received when adding flexible federated identity credential", applicationId) | ||
| } | ||
| if newCredential.Id == nil { | ||
| return fmt.Errorf("api error adding flexible federated identity credential for %s. nil or empty ID received", applicationId) | ||
| } | ||
|
|
||
| id := beta.NewApplicationIdFederatedIdentityCredentialID(applicationId.ApplicationId, *newCredential.Id) | ||
|
|
||
| pollerType := custompollers.NewApplicationFlexibleFederatedCredentialCreationPoller(flexibleFederatedIdentityCredentialClient, id) | ||
| poller := pollers.NewPoller(pollerType, 10*time.Second, pollers.DefaultNumberOfDroppedConnectionsToAllow) | ||
| // Wait for the credential to replicate - TODO This may need converting to a consistency.WaitForUpdate | ||
| if err := poller.PollUntilDone(ctx); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| metadata.SetID(id) | ||
|
|
||
| return nil | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func (f flexibleFederatedIdentityCredentialResource) Read() sdk.ResourceFunc { | ||
| return sdk.ResourceFunc{ | ||
| Timeout: 5 * time.Minute, | ||
| Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { | ||
| federatedIdentityCredentialClient := metadata.Client.Applications.ApplicationFlexibleFederatedIdentityCredential | ||
|
|
||
| state := flexibleFederatedIdentityCredentialModel{} | ||
|
|
||
| id, err := beta.ParseApplicationIdFederatedIdentityCredentialID(metadata.ResourceData.Id()) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| resp, err := federatedIdentityCredentialClient.GetFederatedIdentityCredential(ctx, *id, federatedidentitycredential.DefaultGetFederatedIdentityCredentialOperationOptions()) | ||
| if err != nil { | ||
| if response.WasNotFound(resp.HttpResponse) { | ||
| return metadata.MarkAsGone(id) | ||
| } | ||
|
|
||
| return fmt.Errorf("retrieving %s: %+v", id, err) | ||
| } | ||
|
|
||
| state.ApplicationId = beta.NewApplicationID(id.ApplicationId).ID() | ||
| state.CredentialId = id.FederatedIdentityCredentialId | ||
| if model := resp.Model; model != nil { | ||
| if model.ClaimsMatchingExpression != nil { | ||
| state.ClaimsMatchingExpression = model.ClaimsMatchingExpression.Value | ||
| } | ||
| if len(model.Audiences) > 0 { | ||
| state.Audience = model.Audiences[0] | ||
| } | ||
| state.Description = model.Description.GetOrZero() | ||
| state.DisplayName = model.Name | ||
| state.Issuer = model.Issuer | ||
| } | ||
|
|
||
| return metadata.Encode(&state) | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func (f flexibleFederatedIdentityCredentialResource) Update() sdk.ResourceFunc { | ||
| return sdk.ResourceFunc{ | ||
| Timeout: 5 * time.Minute, | ||
| Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { | ||
| federatedIdentityCredentialClient := metadata.Client.Applications.ApplicationFlexibleFederatedIdentityCredential | ||
|
|
||
| id, err := beta.ParseApplicationIdFederatedIdentityCredentialID(metadata.ResourceData.Id()) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| tf.LockByName(applicationResourceName, id.ApplicationId) | ||
| defer tf.UnlockByName(applicationResourceName, id.ApplicationId) | ||
|
|
||
| data := &flexibleFederatedIdentityCredentialModel{} | ||
|
|
||
| if err := metadata.Decode(data); err != nil { | ||
| return fmt.Errorf("decoding %s: %+v", id, err) | ||
| } | ||
|
|
||
| credential := beta.FederatedIdentityCredential{ | ||
| Id: pointer.To(id.FederatedIdentityCredentialId), | ||
| Audiences: []string{data.Audience}, | ||
| Description: nullable.Value(data.Description), | ||
| Issuer: data.Issuer, | ||
| ClaimsMatchingExpression: &beta.FederatedIdentityExpression{ | ||
| Value: data.ClaimsMatchingExpression, | ||
| LanguageVersion: 1, // Note - from docs: the language version to be used. Should always be set to 1, and is required to be set/sent. | ||
| }, | ||
| // Name is immutable but must be specified as it is a required field | ||
| Name: data.DisplayName, | ||
| } | ||
|
|
||
| if _, err = federatedIdentityCredentialClient.UpdateFederatedIdentityCredential(ctx, *id, credential, federatedidentitycredential.DefaultUpdateFederatedIdentityCredentialOperationOptions()); err != nil { | ||
| return fmt.Errorf("updating %s: %+v", id, err) | ||
| } | ||
|
|
||
| return nil | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func (f flexibleFederatedIdentityCredentialResource) Delete() sdk.ResourceFunc { | ||
| return sdk.ResourceFunc{ | ||
| Timeout: 5 * time.Minute, | ||
| Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { | ||
| federatedIdentityCredentialClient := metadata.Client.Applications.ApplicationFlexibleFederatedIdentityCredential | ||
|
|
||
| id, err := beta.ParseApplicationIdFederatedIdentityCredentialID(metadata.ResourceData.Id()) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| tf.LockByName(applicationResourceName, id.ApplicationId) | ||
| defer tf.UnlockByName(applicationResourceName, id.ApplicationId) | ||
|
|
||
| if _, err = federatedIdentityCredentialClient.DeleteFederatedIdentityCredential(ctx, *id, federatedidentitycredential.DefaultDeleteFederatedIdentityCredentialOperationOptions()); err != nil { | ||
| return fmt.Errorf("deleting %s: %+v", id, err) | ||
| } | ||
|
|
||
| // Wait for credential to be deleted | ||
| if err = consistency.WaitForDeletion(ctx, func(ctx context.Context) (*bool, error) { | ||
| resp, err := federatedIdentityCredentialClient.GetFederatedIdentityCredential(ctx, *id, federatedidentitycredential.DefaultGetFederatedIdentityCredentialOperationOptions()) | ||
| if err != nil { | ||
| if response.WasNotFound(resp.HttpResponse) { | ||
| return pointer.To(false), nil | ||
| } | ||
| return nil, err | ||
| } | ||
| credential := resp.Model | ||
| if credential == nil { | ||
| return pointer.To(false), nil | ||
| } | ||
|
|
||
| return pointer.To(true), nil | ||
| }); err != nil { | ||
| return fmt.Errorf("waiting for deletion of %s: %+v", id, err) | ||
| } | ||
|
|
||
| return nil | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func (f flexibleFederatedIdentityCredentialResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { | ||
| return beta.ValidateApplicationIdFederatedIdentityCredentialID | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.