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

pingfederate_keypairs_signing_csr_export resource #307

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# v0.17.0 (Unreleased)
### Resources
* **New Resource:** `pingfederate_keypairs_signing_csr_export` ([#307](https://github.com/pingidentity/terraform-provider-pingfederate/pull/307))
* **New Resource:** `pingfederate_idp_token_processor` ([#277]([https](https://github.com/pingidentity/terraform-provider-pingfederate/pull/277)))
* **New Resource:** `pingfederate_protocol_metadata_signing_settings` ([#290](https://github.com/pingidentity/terraform-provider-pingfederate/pull/290))
* **New Resource:** `pingfederate_service_authentication` ([#295](https://github.com/pingidentity/terraform-provider-pingfederate/pull/295))
Expand Down
43 changes: 43 additions & 0 deletions docs/resources/keypairs_signing_csr_export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "pingfederate_keypairs_signing_csr_export Resource - terraform-provider-pingfederate"
subcategory: ""
description: |-
Datasource to generate a new certificate signing request (CSR) for a key pair.
---

# pingfederate_keypairs_signing_csr_export (Resource)

Datasource to generate a new certificate signing request (CSR) for a key pair.

## Example Usage

```terraform
// Example of using the time provider to control regular export of CSR
resource "time_rotating" "csr_export" {
rotation_days = 30
}

resource "pingfederate_keypairs_signing_csr_export" "signingCsr" {
keypair_id = "mysigningkeypair"
export_trigger_values = {
"export_rfc3339" : time_rotating.csr_export.rotation_rfc3339,
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `keypair_id` (String) The ID of the keypair.

### Optional

- `export_trigger_values` (Map of String) A meta-argument map of values that, if any values are changed, will force export of a new CSR. Adding values to and removing values from the map will not trigger an export. This parameter can be used to control time-based exports using Terraform.

### Read-Only

- `exported_csr` (String) The exported PEM-encoded certificate signing request.
- `id` (String) The ID of this resource.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Example of using the time provider to control regular export of CSR
resource "time_rotating" "csr_export" {
rotation_days = 30
}

resource "pingfederate_keypairs_signing_csr_export" "signingCsr" {
keypair_id = "mysigningkeypair"
export_trigger_values = {
"export_rfc3339" : time_rotating.csr_export.rotation_rfc3339,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package keypairssigningcsr_test

import (
"testing"

"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/pingidentity/terraform-provider-pingfederate/internal/acctest"
"github.com/pingidentity/terraform-provider-pingfederate/internal/provider"
)

func TestAccKeypairsSigningCsrExportResource(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.ConfigurationPreCheck(t) },
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"pingfederate": providerserver.NewProtocol6WithError(provider.NewTestProvider()),
},
Steps: []resource.TestStep{
{
// Run the export and validate the results
Config: keypairsSigningCsrExportResource_MinimalHCL(),
Check: keypairsSigningCsrExportResource_CheckComputedValues(),
},
{
// Expect no additional rotation
Config: keypairsSigningCsrExportResource_NoExportHCL(),
Check: keypairsSigningCsrExportResource_CheckComputedValues(),
},
{
// Expect rotation
Config: keypairsSigningCsrExportResource_SecondExportHCL(),
Check: keypairsSigningCsrExportResource_CheckComputedValues(),
},
{
// Expect no additional rotation
Config: keypairsSigningCsrExportResource_SecondNoExportHCL(),
Check: keypairsSigningCsrExportResource_CheckComputedValues(),
},
{
// Back to the original with no trigger values
Config: keypairsSigningCsrExportResource_MinimalHCL(),
Check: keypairsSigningCsrExportResource_CheckComputedValues(),
},
},
})
}

func keypairsSigningCsrExportResource_MinimalHCL() string {
return `
resource "pingfederate_keypairs_signing_csr_export" "example" {
keypair_id = "419x9yg43rlawqwq9v6az997k"
}
`
}

func keypairsSigningCsrExportResource_NoExportHCL() string {
return `
resource "pingfederate_keypairs_signing_csr_export" "example" {
keypair_id = "419x9yg43rlawqwq9v6az997k"
export_trigger_values = {
"trigger" = "false"
}
}
`
}

func keypairsSigningCsrExportResource_SecondExportHCL() string {
return `
resource "pingfederate_keypairs_signing_csr_export" "example" {
keypair_id = "419x9yg43rlawqwq9v6az997k"
export_trigger_values = {
"trigger" = "updated"
"newtrigger" = "new"
}
}
`
}

func keypairsSigningCsrExportResource_SecondNoExportHCL() string {
return `
resource "pingfederate_keypairs_signing_csr_export" "example" {
keypair_id = "419x9yg43rlawqwq9v6az997k"
export_trigger_values = {
"trigger" = "updated"
}
}
`
}

// Validate any computed values when applying HCL
func keypairsSigningCsrExportResource_CheckComputedValues() resource.TestCheckFunc {
return resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("pingfederate_keypairs_signing_csr_export.example", "exported_csr"),
resource.TestCheckResourceAttr("pingfederate_keypairs_signing_csr_export.example", "id", "419x9yg43rlawqwq9v6az997k"),
)
}
2 changes: 2 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import (
keypairsoauthopenidconnectadditionalkeysets "github.com/pingidentity/terraform-provider-pingfederate/internal/resource/config/keypairs/oauthopenidconnect/additionalkeysets"
keypairsigning "github.com/pingidentity/terraform-provider-pingfederate/internal/resource/config/keypairs/signing"
keypairssigningcertificate "github.com/pingidentity/terraform-provider-pingfederate/internal/resource/config/keypairs/signing/certificate"
keypairssigningcsr "github.com/pingidentity/terraform-provider-pingfederate/internal/resource/config/keypairs/signing/csr"
keypairsigningimport "github.com/pingidentity/terraform-provider-pingfederate/internal/resource/config/keypairs/signing/import"
keypairssigningrotationsettings "github.com/pingidentity/terraform-provider-pingfederate/internal/resource/config/keypairs/signing/rotationsettings"
keypairssslserver "github.com/pingidentity/terraform-provider-pingfederate/internal/resource/config/keypairs/sslserver"
Expand Down Expand Up @@ -773,6 +774,7 @@ func (p *pingfederateProvider) Resources(_ context.Context) []func() resource.Re
keypairsoauthopenidconnectadditionalkeysets.KeypairsOauthOpenidConnectAdditionalKeySetResource,
keypairsigning.KeypairsSigningKeyResource,
keypairsigningimport.KeyPairsSigningImportResource,
keypairssigningcsr.KeypairsSigningCsrExportResource,
keypairssigningrotationsettings.KeypairsSigningKeyRotationSettingsResource,
keypairssslserver.KeypairsSslServerKeyResource,
keypairsslserverimport.KeyPairsSslServerImportResource,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package keypairssigningcsr

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
client "github.com/pingidentity/pingfederate-go-client/v1210/configurationapi"
"github.com/pingidentity/terraform-provider-pingfederate/internal/resource/common/id"
"github.com/pingidentity/terraform-provider-pingfederate/internal/resource/config"
"github.com/pingidentity/terraform-provider-pingfederate/internal/resource/providererror"
internaltypes "github.com/pingidentity/terraform-provider-pingfederate/internal/types"
)

var (
_ resource.Resource = &keypairsSigningCsrExportResource{}
_ resource.ResourceWithConfigure = &keypairsSigningCsrExportResource{}

customId = "keypair_id"
)

func KeypairsSigningCsrExportResource() resource.Resource {
return &keypairsSigningCsrExportResource{}
}

type keypairsSigningCsrExportResource struct {
providerConfig internaltypes.ProviderConfiguration
apiClient *client.APIClient
}

func (r *keypairsSigningCsrExportResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_keypairs_signing_csr_export"
}

func (r *keypairsSigningCsrExportResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

providerCfg := req.ProviderData.(internaltypes.ResourceConfiguration)
r.providerConfig = providerCfg.ProviderConfig
r.apiClient = providerCfg.ApiClient
}

type keypairsSigningCsrExportResourceModel struct {
Id types.String `tfsdk:"id"`
KeypairId types.String `tfsdk:"keypair_id"`
ExportedCsr types.String `tfsdk:"exported_csr"`
ExportTriggerValues types.Map `tfsdk:"export_trigger_values"`
}

func (r *keypairsSigningCsrExportResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Datasource to generate a new certificate signing request (CSR) for a key pair.",
Attributes: map[string]schema.Attribute{
"keypair_id": schema.StringAttribute{
Description: "The ID of the keypair.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"exported_csr": schema.StringAttribute{
Description: "The exported PEM-encoded certificate signing request.",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"export_trigger_values": schema.MapAttribute{
Description: "A meta-argument map of values that, if any values are changed, will force export of a new CSR. Adding values to and removing values from the map will not trigger an export. This parameter can be used to control time-based exports using Terraform.",
Optional: true,
ElementType: types.StringType,
},
},
}
id.ToSchema(&resp.Schema)
}

// Export a new CSR via RequiresReplace when the trigger values change
func (r *keypairsSigningCsrExportResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
// Destruction plan
if req.Plan.Raw.IsNull() {
return
}

var plan, state types.Map
var planValues, stateValues map[string]attr.Value

resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("export_trigger_values"), &plan)...)
if resp.Diagnostics.HasError() {
return
}

planValues = plan.Elements()

resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("export_trigger_values"), &state)...)
if resp.Diagnostics.HasError() {
return
}

stateValues = state.Elements()

for k, v := range planValues {
if stateValue, ok := stateValues[k]; ok && (v == types.StringUnknown() || !stateValue.Equal(v)) {
resp.RequiresReplace = path.Paths{path.Root("export_trigger_values")}
break
}
}
}

func (r *keypairsSigningCsrExportResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data keypairsSigningCsrExportResourceModel

// Read Terraform config data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

// Read API call logic
exportRequest := r.apiClient.KeyPairsSigningAPI.ExportCsr(config.AuthContext(ctx, r.providerConfig), data.KeypairId.ValueString())
responseData, httpResp, err := exportRequest.Execute()
if err != nil {
config.ReportHttpErrorCustomId(ctx, &resp.Diagnostics, "An error occurred while generating the certificate signing request.", err, httpResp, &customId)
return
}

// Set the exported metadata
data.Id = types.StringValue(data.KeypairId.ValueString())
data.ExportedCsr = types.StringValue(responseData)

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *keypairsSigningCsrExportResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
// PingFederate provides no read endpoint for this resource, so we'll just maintain whatever is in state
resp.State.Raw = req.State.Raw
}

func (r *keypairsSigningCsrExportResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
// This will only happen when adding or removing export trigger values.
// Just copy the existing state and export_trigger_values into state.
var plan, state keypairsSigningCsrExportResourceModel

// Read Terraform config data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
state.ExportTriggerValues = plan.ExportTriggerValues

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *keypairsSigningCsrExportResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
// There is no way to delete an exported CSR
providererror.WarnConfigurationCannotBeReset("pingfederate_keypairs_signing_csr_export", &resp.Diagnostics)
}
15 changes: 14 additions & 1 deletion internal/resource/config/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,26 @@ func ReportHttpErrorCustomId(ctx context.Context, diagnostics *diag.Diagnostics,
var pfError pingFederateErrorResponse
internalError = json.Unmarshal(body, &pfError)
if internalError == nil {
if len(pfError.ValidationErrors) == 0 {
var errorDetail strings.Builder
errorDetail.WriteString("Error summary: ")
errorDetail.WriteString(errorSummary)
errorDetail.WriteString("\nMessage: ")
errorDetail.WriteString(pfError.Message)
errorDetail.WriteString("\nHTTP status: ")
errorDetail.WriteString(httpResp.Status)
errorDetail.WriteString("\nResult ID: ")
errorDetail.WriteString(pfError.ResultId)
diagnostics.AddError(providererror.PingFederateAPIError, errorDetail.String())
}
for _, validationError := range pfError.ValidationErrors {
var errorDetail strings.Builder
errorDetail.WriteString("Error summary: ")
errorDetail.WriteString(errorSummary)
errorDetail.WriteString("\nMessage: ")
errorDetail.WriteString(validationError.Message)
errorDetail.WriteString("\nHTTP status: " + httpResp.Status)
errorDetail.WriteString("\nHTTP status: ")
errorDetail.WriteString(httpResp.Status)
if validationError.FieldPath != "" {
errorDetail.WriteString("\nPingFederate field path: ")
errorDetail.WriteString(validationError.FieldPath)
Expand Down
1 change: 1 addition & 0 deletions scripts/verifyContent.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

noImportResources = [
"pingfederate_connection_metadata_export",
"pingfederate_keypairs_signing_csr_export",
"pingfederate_keypairs_signing_key",
"pingfederate_keypairs_ssl_server_key",
"pingfederate_license"
Expand Down
Loading