From 7b128ac0380cc7e0c11cb74a001b3a108e45ed19 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Fri, 7 Jul 2023 12:24:23 +0300 Subject: [PATCH 01/19] feat: add tfe_saml_settings resource schema and metadata --- tfe/provider_next.go | 1 + tfe/resource_tfe_saml_settings.go | 226 ++++++++++++++++++++++++++++++ tfe/tool_helpers.go | 33 +++++ 3 files changed, 260 insertions(+) create mode 100644 tfe/resource_tfe_saml_settings.go diff --git a/tfe/provider_next.go b/tfe/provider_next.go index 28cdc07f7..591bd6a97 100644 --- a/tfe/provider_next.go +++ b/tfe/provider_next.go @@ -119,5 +119,6 @@ func (p *frameworkProvider) DataSources(ctx context.Context) []func() datasource func (p *frameworkProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ NewResourceVariable, + NewSAMLSettingsResource, } } diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go new file mode 100644 index 000000000..458e427b1 --- /dev/null +++ b/tfe/resource_tfe_saml_settings.go @@ -0,0 +1,226 @@ +package tfe + +import ( + "context" + "fmt" + tfe "github.com/hashicorp/go-tfe" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +const ( + signatureMethodSHA1 string = "SHA1" + signatureMethodSHA256 string = "SHA256" + defaultSSOAPITokenSessionTimeoutSeconds int64 = 1209600 // 14 days +) + +type resourceTFESAMLSettings struct { + //config ConfiguredClient + client *tfe.Client +} + +// modelTFESAMLSettings maps the resource schema data to a struct. +type modelTFESAMLSettings struct { + ID types.String `tfsdk:"id"` + Enabled types.Bool `tfsdk:"enabled"` + Debug types.Bool `tfsdk:"debug"` + AuthnRequestsSigned types.Bool `tfsdk:"authn_requests_signed"` + WantAssertionsSigned types.Bool `tfsdk:"want_assertions_signed"` + TeamManagementEnabled types.Bool `tfsdk:"team_management_enabled"` + OldIDPCert types.String `tfsdk:"old_idp_cert"` + IDPCert types.String `tfsdk:"idp_cert"` + SLOEndpointURL types.String `tfsdk:"slo_endpoint_url"` + SSOEndpointURL types.String `tfsdk:"sso_endpoint_url"` + AttrUsername types.String `tfsdk:"attr_username"` + AttrGroups types.String `tfsdk:"attr_groups"` + AttrSiteAdmin types.String `tfsdk:"attr_site_admin"` + SiteAdminRole types.String `tfsdk:"site_admin_role"` + SSOAPITokenSessionTimeout types.Int64 `tfsdk:"sso_api_token_session_timeout"` + ACSConsumerURL types.String `tfsdk:"acs_consumer_url"` + MetadataURL types.String `tfsdk:"metadata_url"` + Certificate types.String `tfsdk:"certificate"` + PrivateKey types.String `tfsdk:"private_key"` + SignatureSigningMethod types.String `tfsdk:"signature_signing_method"` + SignatureDigestMethod types.String `tfsdk:"signature_digest_method"` +} + +// Configure implements resource.ResourceWithConfigure +func (r *resourceTFESAMLSettings) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Early exit if provider is unconfigured (i.e. we're only validating config or something) + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(ConfiguredClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected resource Configure type", + fmt.Sprintf("Expected tfe.ConfiguredClient, got %T. This is a bug in the tfe provider, so please report it on GitHub.", req.ProviderData), + ) + } + r.client = client.Client +} + +// Metadata implements resource.Resource +func (r *resourceTFESAMLSettings) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_saml_settings" +} + +// Schema implements resource.Resource +func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Whether or not to enable SAML single sign-on", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "debug": schema.BoolAttribute{ + Description: "When sign-on fails and this is enabled, the SAMLResponse XML will be displayed on the login page", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "authn_requests_signed": schema.BoolAttribute{ + Description: "Ensure that messages are signed", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "want_assertions_signed": schema.BoolAttribute{ + Description: "Ensure that elements are signed", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "team_management_enabled": schema.BoolAttribute{ + Description: "Set it to false if you would rather use Terraform Enterprise to manage team membership", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "old_idp_cert": schema.StringAttribute{ + Computed: true, + }, + "idp_cert": schema.StringAttribute{ + Description: "Identity Provider Certificate specifies the PEM encoded X.509 Certificate as provided by the IdP configuration", + Optional: true, + Computed: true, + }, + "slo_endpoint_url": schema.StringAttribute{ + Description: "Single Log Out URL specifies the HTTPS endpoint on your IdP for single logout requests. This value is provided by the IdP configuration", + Optional: true, + Computed: true, + }, + "sso_endpoint_url": schema.StringAttribute{ + Description: "Single Sign On URL specifies the HTTPS endpoint on your IdP for single sign-on requests. This value is provided by the IdP configuration", + Optional: true, + Computed: true, + }, + "attr_username": schema.StringAttribute{ + Description: "Username Attribute Name specifies the name of the SAML attribute that determines the user's username", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("Username"), + }, + "attr_site_admin": schema.StringAttribute{ + Description: "Specifies the role for site admin access. Overrides the \"Site Admin Role\" method", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("SiteAdmin"), + }, + "attr_groups": schema.StringAttribute{ + Description: "Team Attribute Name specifies the name of the SAML attribute that determines team membership", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("MemberOf"), + }, + "site_admin_role": schema.StringAttribute{ + Description: "Specifies the role for site admin access, provided in the list of roles sent in the Team Attribute Name attribute", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("site-admins"), + }, + "sso_api_token_session_timeout": schema.Int64Attribute{ + Description: "Specifies the Single Sign On session timeout in seconds. Defaults to 14 days", + Optional: true, + Computed: true, + Default: staticInt64(defaultSSOAPITokenSessionTimeoutSeconds), + }, + "acs_consumer_url": schema.StringAttribute{ + Description: "ACS Consumer (Recipient) URL", + Computed: true, + }, + "metadata_url": schema.StringAttribute{ + Description: "Metadata (Audience) URL", + Computed: true, + }, + "certificate": schema.StringAttribute{ + Description: "The certificate used for request and assertion signing", + Optional: true, + Computed: true, + }, + "private_key": schema.StringAttribute{ + Description: "The private key used for request and assertion signing", + Optional: true, + Computed: true, + Sensitive: true, + }, + "signature_signing_method": schema.StringAttribute{ + Description: fmt.Sprintf("Signature Signing Method. Must be either `%s` or `%s`. Defaults to `%s`", signatureMethodSHA1, signatureMethodSHA256, signatureMethodSHA256), + Optional: true, + Computed: true, + Default: stringdefault.StaticString(signatureMethodSHA256), + Validators: []validator.String{ + stringvalidator.OneOf( + signatureMethodSHA1, + signatureMethodSHA256, + ), + }, + }, + "signature_digest_method": schema.StringAttribute{ + Description: fmt.Sprintf("Signature Digest Method. Must be either `%s` or `%s`. Defaults to `%s`", signatureMethodSHA1, signatureMethodSHA256, signatureMethodSHA256), + Optional: true, + Computed: true, + Default: stringdefault.StaticString(signatureMethodSHA256), + Validators: []validator.String{ + stringvalidator.OneOf( + signatureMethodSHA1, + signatureMethodSHA256, + ), + }, + }, + }, + Version: 1, + } +} + +func (r *resourceTFESAMLSettings) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +} + +func (r *resourceTFESAMLSettings) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { +} + +func (r *resourceTFESAMLSettings) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +} + +func (r resourceTFESAMLSettings) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +var ( + _ resource.Resource = &resourceTFESAMLSettings{} + _ resource.ResourceWithConfigure = &resourceTFESAMLSettings{} +) + +// NewSAMLSettingsResource is a resource function for the framework provider. +func NewSAMLSettingsResource() resource.Resource { + return &resourceTFESAMLSettings{} +} diff --git a/tfe/tool_helpers.go b/tfe/tool_helpers.go index fde0958bb..f57835666 100644 --- a/tfe/tool_helpers.go +++ b/tfe/tool_helpers.go @@ -4,7 +4,10 @@ package tfe import ( + "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" + "github.com/hashicorp/terraform-plugin-framework/types" tfe "github.com/hashicorp/go-tfe" ) @@ -51,3 +54,33 @@ func fetchTerraformVersionID(version string, client *tfe.Client) (string, error) return "", fmt.Errorf("terraform version not found") } + +// staticInt64 returns a static Int64 value default handler. +// +// Use staticInt64 if a static default value for a Int64 should be set. +func staticInt64(defaultVal int64) defaults.Int64 { + return staticInt64Default{ + defaultVal: defaultVal, + } +} + +// staticStringDefault is static value default handler that +// sets a value on a string attribute. +type staticInt64Default struct { + defaultVal int64 +} + +// Description returns a human-readable description of the default value handler. +func (d staticInt64Default) Description(_ context.Context) string { + return fmt.Sprintf("value defaults to %d", d.defaultVal) +} + +// MarkdownDescription returns a markdown description of the default value handler. +func (d staticInt64Default) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("value defaults to `%d`", d.defaultVal) +} + +// DefaultInt64 implements the static default value logic. +func (d staticInt64Default) DefaultInt64(_ context.Context, req defaults.Int64Request, resp *defaults.Int64Response) { + resp.PlanValue = types.Int64Value(d.defaultVal) +} From 6457922caa2e857642e2064e1ed3e90aceb20500 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Fri, 7 Jul 2023 12:57:43 +0300 Subject: [PATCH 02/19] feat: add tfe_saml_settings resource read and create --- tfe/resource_tfe_saml_settings.go | 97 ++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 13 deletions(-) diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index 458e427b1..54118c8e4 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) const ( @@ -20,7 +21,6 @@ const ( ) type resourceTFESAMLSettings struct { - //config ConfiguredClient client *tfe.Client } @@ -49,9 +49,36 @@ type modelTFESAMLSettings struct { SignatureDigestMethod types.String `tfsdk:"signature_digest_method"` } +// modelFromTFEAdminSAMLSettings builds a modelTFESAMLSettings struct from a tfe.AdminSAMLSetting value +func modelFromTFEAdminSAMLSettings(v tfe.AdminSAMLSetting, signatureSigningMethod, signatureDigestMethod string) modelTFESAMLSettings { + return modelTFESAMLSettings{ + ID: types.StringValue(v.ID), + Enabled: types.BoolValue(v.Enabled), + Debug: types.BoolValue(v.Debug), + AuthnRequestsSigned: types.BoolValue(v.AuthnRequestsSigned), + WantAssertionsSigned: types.BoolValue(v.WantAssertionsSigned), + TeamManagementEnabled: types.BoolValue(v.TeamManagementEnabled), + OldIDPCert: types.StringValue(v.OldIDPCert), + IDPCert: types.StringValue(v.IDPCert), + SLOEndpointURL: types.StringValue(v.SLOEndpointURL), + SSOEndpointURL: types.StringValue(v.SSOEndpointURL), + AttrUsername: types.StringValue(v.AttrUsername), + AttrGroups: types.StringValue(v.AttrGroups), + AttrSiteAdmin: types.StringValue(v.AttrSiteAdmin), + SiteAdminRole: types.StringValue(v.SiteAdminRole), + SSOAPITokenSessionTimeout: types.Int64Value(int64(v.SSOAPITokenSessionTimeout)), + ACSConsumerURL: types.StringValue(v.ACSConsumerURL), + MetadataURL: types.StringValue(v.MetadataURL), + Certificate: types.StringValue(v.Certificate), + PrivateKey: types.StringValue(v.PrivateKey), + SignatureSigningMethod: types.StringValue(signatureSigningMethod), + SignatureDigestMethod: types.StringValue(signatureDigestMethod), + } +} + // Configure implements resource.ResourceWithConfigure func (r *resourceTFESAMLSettings) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - // Early exit if provider is unconfigured (i.e. we're only validating config or something) + // Early exit if provider is not properly configured (i.e. we're only validating config or something) if req.ProviderData == nil { return } @@ -78,10 +105,8 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem Computed: true, }, "enabled": schema.BoolAttribute{ - Description: "Whether or not to enable SAML single sign-on", - Optional: true, + Description: "Whether or not SAML single sign-on is enabled", Computed: true, - Default: booldefault.StaticBool(false), }, "debug": schema.BoolAttribute{ Description: "When sign-on fails and this is enabled, the SAMLResponse XML will be displayed on the login page", @@ -112,18 +137,15 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem }, "idp_cert": schema.StringAttribute{ Description: "Identity Provider Certificate specifies the PEM encoded X.509 Certificate as provided by the IdP configuration", - Optional: true, - Computed: true, + Required: true, }, "slo_endpoint_url": schema.StringAttribute{ Description: "Single Log Out URL specifies the HTTPS endpoint on your IdP for single logout requests. This value is provided by the IdP configuration", - Optional: true, - Computed: true, + Required: true, }, "sso_endpoint_url": schema.StringAttribute{ Description: "Single Sign On URL specifies the HTTPS endpoint on your IdP for single sign-on requests. This value is provided by the IdP configuration", - Optional: true, - Computed: true, + Required: true, }, "attr_username": schema.StringAttribute{ Description: "Username Attribute Name specifies the name of the SAML attribute that determines the user's username", @@ -176,7 +198,6 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem }, "signature_signing_method": schema.StringAttribute{ Description: fmt.Sprintf("Signature Signing Method. Must be either `%s` or `%s`. Defaults to `%s`", signatureMethodSHA1, signatureMethodSHA256, signatureMethodSHA256), - Optional: true, Computed: true, Default: stringdefault.StaticString(signatureMethodSHA256), Validators: []validator.String{ @@ -188,7 +209,6 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem }, "signature_digest_method": schema.StringAttribute{ Description: fmt.Sprintf("Signature Digest Method. Must be either `%s` or `%s`. Defaults to `%s`", signatureMethodSHA1, signatureMethodSHA256, signatureMethodSHA256), - Optional: true, Computed: true, Default: stringdefault.StaticString(signatureMethodSHA256), Validators: []validator.String{ @@ -204,9 +224,60 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem } func (r *resourceTFESAMLSettings) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data modelTFESAMLSettings + diags := req.State.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + samlSettings, err := r.client.Admin.Settings.SAML.Read(ctx) + if err != nil { + resp.Diagnostics.AddError( + "Error reading SAML Settings", + "Could not read SAML Settings, unexpected error: "+err.Error(), + ) + return + } + result := modelFromTFEAdminSAMLSettings(*samlSettings, data.SignatureSigningMethod.ValueString(), data.SignatureDigestMethod.ValueString()) + diags = resp.State.Set(ctx, &result) + resp.Diagnostics.Append(diags...) } func (r *resourceTFESAMLSettings) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data modelTFESAMLSettings + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + //TODO: add more after we upgrade go-tfe + options := tfe.AdminSAMLSettingsUpdateOptions{ + Enabled: basetypes.NewBoolValue(true).ValueBoolPointer(), + Debug: data.Debug.ValueBoolPointer(), + IDPCert: data.IDPCert.ValueStringPointer(), + SLOEndpointURL: data.SLOEndpointURL.ValueStringPointer(), + SSOEndpointURL: data.SSOEndpointURL.ValueStringPointer(), + AttrUsername: data.AttrUsername.ValueStringPointer(), + AttrGroups: data.AttrGroups.ValueStringPointer(), + AttrSiteAdmin: data.AttrSiteAdmin.ValueStringPointer(), + SiteAdminRole: data.SiteAdminRole.ValueStringPointer(), + SSOAPITokenSessionTimeout: tfe.Int(int(data.SSOAPITokenSessionTimeout.ValueInt64())), + } + + samlSettings, err := r.client.Admin.Settings.SAML.Update(ctx, options) + if err != nil { + resp.Diagnostics.AddError( + "Error updating SAML Settings", + "Could not set SAML Settings, unexpected error: "+err.Error(), + ) + return + } + + result := modelFromTFEAdminSAMLSettings(*samlSettings, data.SignatureSigningMethod.ValueString(), data.SignatureDigestMethod.ValueString()) + diags = resp.State.Set(ctx, &result) + resp.Diagnostics.Append(diags...) } func (r *resourceTFESAMLSettings) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { From c17aa02fdcf71c8dacd3b7a775e1043a330b5b83 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Fri, 7 Jul 2023 13:15:37 +0300 Subject: [PATCH 03/19] feat: add tfe_saml_settings resource update functionality --- tfe/resource_tfe_saml_settings.go | 41 +++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index 54118c8e4..c97d0d15d 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "time" ) const ( @@ -47,6 +48,7 @@ type modelTFESAMLSettings struct { PrivateKey types.String `tfsdk:"private_key"` SignatureSigningMethod types.String `tfsdk:"signature_signing_method"` SignatureDigestMethod types.String `tfsdk:"signature_digest_method"` + LastUpdated types.String `tfsdk:"last_updated"` } // modelFromTFEAdminSAMLSettings builds a modelTFESAMLSettings struct from a tfe.AdminSAMLSetting value @@ -187,12 +189,10 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem }, "certificate": schema.StringAttribute{ Description: "The certificate used for request and assertion signing", - Optional: true, Computed: true, }, "private_key": schema.StringAttribute{ Description: "The private key used for request and assertion signing", - Optional: true, Computed: true, Sensitive: true, }, @@ -218,6 +218,10 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem ), }, }, + "last_updated": schema.StringAttribute{ + Description: "Time when resource was last updated", + Computed: true, + }, }, Version: 1, } @@ -281,6 +285,39 @@ func (r *resourceTFESAMLSettings) Create(ctx context.Context, req resource.Creat } func (r *resourceTFESAMLSettings) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data modelTFESAMLSettings + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + options := tfe.AdminSAMLSettingsUpdateOptions{ + Enabled: basetypes.NewBoolValue(true).ValueBoolPointer(), + Debug: data.Debug.ValueBoolPointer(), + IDPCert: data.IDPCert.ValueStringPointer(), + SLOEndpointURL: data.SLOEndpointURL.ValueStringPointer(), + SSOEndpointURL: data.SSOEndpointURL.ValueStringPointer(), + AttrUsername: data.AttrUsername.ValueStringPointer(), + AttrGroups: data.AttrGroups.ValueStringPointer(), + AttrSiteAdmin: data.AttrSiteAdmin.ValueStringPointer(), + SiteAdminRole: data.SiteAdminRole.ValueStringPointer(), + SSOAPITokenSessionTimeout: tfe.Int(int(data.SSOAPITokenSessionTimeout.ValueInt64())), + } + + samlSettings, err := r.client.Admin.Settings.SAML.Update(ctx, options) + if err != nil { + resp.Diagnostics.AddError( + "Error updating SAML Settings", + "Could not set SAML Settings, unexpected error: "+err.Error(), + ) + return + } + result := modelFromTFEAdminSAMLSettings(*samlSettings, data.SignatureSigningMethod.ValueString(), data.SignatureDigestMethod.ValueString()) + result.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) + + diags = resp.State.Set(ctx, &result) + resp.Diagnostics.Append(diags...) } func (r resourceTFESAMLSettings) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { From e8775bf5631a5b01a2d7d3f5e337726ed4b1e2ad Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Fri, 7 Jul 2023 14:41:32 +0300 Subject: [PATCH 04/19] refactor: use updateSAMLSettings in 2 places instead of duplicating code --- tfe/resource_tfe_saml_settings.go | 74 +++++++++++-------------------- 1 file changed, 27 insertions(+), 47 deletions(-) diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index c97d0d15d..53c1443f8 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -237,10 +237,7 @@ func (r *resourceTFESAMLSettings) Read(ctx context.Context, req resource.ReadReq samlSettings, err := r.client.Admin.Settings.SAML.Read(ctx) if err != nil { - resp.Diagnostics.AddError( - "Error reading SAML Settings", - "Could not read SAML Settings, unexpected error: "+err.Error(), - ) + resp.Diagnostics.AddError("Error reading SAML Settings", "Could not read SAML Settings, unexpected error: "+err.Error()) return } result := modelFromTFEAdminSAMLSettings(*samlSettings, data.SignatureSigningMethod.ValueString(), data.SignatureDigestMethod.ValueString()) @@ -249,71 +246,39 @@ func (r *resourceTFESAMLSettings) Read(ctx context.Context, req resource.ReadReq } func (r *resourceTFESAMLSettings) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data modelTFESAMLSettings - diags := req.Plan.Get(ctx, &data) + var m modelTFESAMLSettings + diags := req.Plan.Get(ctx, &m) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - //TODO: add more after we upgrade go-tfe - options := tfe.AdminSAMLSettingsUpdateOptions{ - Enabled: basetypes.NewBoolValue(true).ValueBoolPointer(), - Debug: data.Debug.ValueBoolPointer(), - IDPCert: data.IDPCert.ValueStringPointer(), - SLOEndpointURL: data.SLOEndpointURL.ValueStringPointer(), - SSOEndpointURL: data.SSOEndpointURL.ValueStringPointer(), - AttrUsername: data.AttrUsername.ValueStringPointer(), - AttrGroups: data.AttrGroups.ValueStringPointer(), - AttrSiteAdmin: data.AttrSiteAdmin.ValueStringPointer(), - SiteAdminRole: data.SiteAdminRole.ValueStringPointer(), - SSOAPITokenSessionTimeout: tfe.Int(int(data.SSOAPITokenSessionTimeout.ValueInt64())), - } - - samlSettings, err := r.client.Admin.Settings.SAML.Update(ctx, options) + samlSettings, err := r.updateSAMLSettings(ctx, m) if err != nil { - resp.Diagnostics.AddError( - "Error updating SAML Settings", - "Could not set SAML Settings, unexpected error: "+err.Error(), - ) + resp.Diagnostics.AddError("Error creating SAML Settings", "Could not set SAML Settings, unexpected error: "+err.Error()) return } - result := modelFromTFEAdminSAMLSettings(*samlSettings, data.SignatureSigningMethod.ValueString(), data.SignatureDigestMethod.ValueString()) + result := modelFromTFEAdminSAMLSettings(*samlSettings, m.SignatureSigningMethod.ValueString(), m.SignatureDigestMethod.ValueString()) diags = resp.State.Set(ctx, &result) resp.Diagnostics.Append(diags...) } func (r *resourceTFESAMLSettings) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data modelTFESAMLSettings - diags := req.Plan.Get(ctx, &data) + var m modelTFESAMLSettings + diags := req.Plan.Get(ctx, &m) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - options := tfe.AdminSAMLSettingsUpdateOptions{ - Enabled: basetypes.NewBoolValue(true).ValueBoolPointer(), - Debug: data.Debug.ValueBoolPointer(), - IDPCert: data.IDPCert.ValueStringPointer(), - SLOEndpointURL: data.SLOEndpointURL.ValueStringPointer(), - SSOEndpointURL: data.SSOEndpointURL.ValueStringPointer(), - AttrUsername: data.AttrUsername.ValueStringPointer(), - AttrGroups: data.AttrGroups.ValueStringPointer(), - AttrSiteAdmin: data.AttrSiteAdmin.ValueStringPointer(), - SiteAdminRole: data.SiteAdminRole.ValueStringPointer(), - SSOAPITokenSessionTimeout: tfe.Int(int(data.SSOAPITokenSessionTimeout.ValueInt64())), - } - - samlSettings, err := r.client.Admin.Settings.SAML.Update(ctx, options) + samlSettings, err := r.updateSAMLSettings(ctx, m) if err != nil { - resp.Diagnostics.AddError( - "Error updating SAML Settings", - "Could not set SAML Settings, unexpected error: "+err.Error(), - ) + resp.Diagnostics.AddError("Error updating SAML Settings", "Could not set SAML Settings, unexpected error: "+err.Error()) return } - result := modelFromTFEAdminSAMLSettings(*samlSettings, data.SignatureSigningMethod.ValueString(), data.SignatureDigestMethod.ValueString()) + + result := modelFromTFEAdminSAMLSettings(*samlSettings, m.SignatureSigningMethod.ValueString(), m.SignatureDigestMethod.ValueString()) result.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) diags = resp.State.Set(ctx, &result) @@ -332,3 +297,18 @@ var ( func NewSAMLSettingsResource() resource.Resource { return &resourceTFESAMLSettings{} } + +func (r *resourceTFESAMLSettings) updateSAMLSettings(ctx context.Context, m modelTFESAMLSettings) (*tfe.AdminSAMLSetting, error) { + return r.client.Admin.Settings.SAML.Update(ctx, tfe.AdminSAMLSettingsUpdateOptions{ + Enabled: basetypes.NewBoolValue(true).ValueBoolPointer(), + Debug: m.Debug.ValueBoolPointer(), + IDPCert: m.IDPCert.ValueStringPointer(), + SLOEndpointURL: m.SLOEndpointURL.ValueStringPointer(), + SSOEndpointURL: m.SSOEndpointURL.ValueStringPointer(), + AttrUsername: m.AttrUsername.ValueStringPointer(), + AttrGroups: m.AttrGroups.ValueStringPointer(), + AttrSiteAdmin: m.AttrSiteAdmin.ValueStringPointer(), + SiteAdminRole: m.SiteAdminRole.ValueStringPointer(), + SSOAPITokenSessionTimeout: tfe.Int(int(m.SSOAPITokenSessionTimeout.ValueInt64())), + }) +} From 0066806854b9b6b375551f8741e2138c65e47c66 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Fri, 7 Jul 2023 15:02:51 +0300 Subject: [PATCH 05/19] feat: add tfe_saml_settings resource delete functionality --- tfe/resource_tfe_saml_settings.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index 53c1443f8..5e267e8fb 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -3,6 +3,8 @@ package tfe import ( "context" "fmt" + "time" + tfe "github.com/hashicorp/go-tfe" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -12,7 +14,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - "time" ) const ( @@ -21,11 +22,12 @@ const ( defaultSSOAPITokenSessionTimeoutSeconds int64 = 1209600 // 14 days ) +// resourceTFESAMLSettings implements the tfe_saml_settings resource type type resourceTFESAMLSettings struct { client *tfe.Client } -// modelTFESAMLSettings maps the resource schema data to a struct. +// modelTFESAMLSettings maps the resource schema data to a struct type modelTFESAMLSettings struct { ID types.String `tfsdk:"id"` Enabled types.Bool `tfsdk:"enabled"` @@ -227,6 +229,7 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem } } +// Read implements resource.Resource func (r *resourceTFESAMLSettings) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var data modelTFESAMLSettings diags := req.State.Get(ctx, &data) @@ -240,11 +243,13 @@ func (r *resourceTFESAMLSettings) Read(ctx context.Context, req resource.ReadReq resp.Diagnostics.AddError("Error reading SAML Settings", "Could not read SAML Settings, unexpected error: "+err.Error()) return } + result := modelFromTFEAdminSAMLSettings(*samlSettings, data.SignatureSigningMethod.ValueString(), data.SignatureDigestMethod.ValueString()) diags = resp.State.Set(ctx, &result) resp.Diagnostics.Append(diags...) } +// Create implements resource.Resource func (r *resourceTFESAMLSettings) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var m modelTFESAMLSettings diags := req.Plan.Get(ctx, &m) @@ -264,6 +269,7 @@ func (r *resourceTFESAMLSettings) Create(ctx context.Context, req resource.Creat resp.Diagnostics.Append(diags...) } +// Update implements resource.Resource func (r *resourceTFESAMLSettings) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var m modelTFESAMLSettings diags := req.Plan.Get(ctx, &m) @@ -285,7 +291,22 @@ func (r *resourceTFESAMLSettings) Update(ctx context.Context, req resource.Updat resp.Diagnostics.Append(diags...) } +// Delete disables the SAML Settings and then removes the resource from the state file. You cannot delete TFE SAML Settings, only disable them func (r resourceTFESAMLSettings) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var m modelTFESAMLSettings + diags := req.State.Get(ctx, &m) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + _, err := r.client.Admin.Settings.SAML.Update(ctx, tfe.AdminSAMLSettingsUpdateOptions{ + Enabled: basetypes.NewBoolValue(false).ValueBoolPointer(), + }) + if err != nil { + resp.Diagnostics.AddError("Error deleting SAML Settings", "Could not disable SAML Settings, unexpected error: "+err.Error()) + return + } } var ( @@ -298,6 +319,7 @@ func NewSAMLSettingsResource() resource.Resource { return &resourceTFESAMLSettings{} } +// updateSAMLSettings was created to keep the code DRY. It is used in both Create and Update functions func (r *resourceTFESAMLSettings) updateSAMLSettings(ctx context.Context, m modelTFESAMLSettings) (*tfe.AdminSAMLSetting, error) { return r.client.Admin.Settings.SAML.Update(ctx, tfe.AdminSAMLSettingsUpdateOptions{ Enabled: basetypes.NewBoolValue(true).ValueBoolPointer(), From 4acc5dce3afd94ffcbaeca86d94deecd1e6eaff1 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Fri, 7 Jul 2023 15:10:07 +0300 Subject: [PATCH 06/19] feat: add tfe_saml_settings resource import functionality --- tfe/resource_tfe_saml_settings.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index 5e267e8fb..d75c13f96 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -231,8 +231,8 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem // Read implements resource.Resource func (r *resourceTFESAMLSettings) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data modelTFESAMLSettings - diags := req.State.Get(ctx, &data) + var m modelTFESAMLSettings + diags := req.State.Get(ctx, &m) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return @@ -244,7 +244,7 @@ func (r *resourceTFESAMLSettings) Read(ctx context.Context, req resource.ReadReq return } - result := modelFromTFEAdminSAMLSettings(*samlSettings, data.SignatureSigningMethod.ValueString(), data.SignatureDigestMethod.ValueString()) + result := modelFromTFEAdminSAMLSettings(*samlSettings, m.SignatureSigningMethod.ValueString(), m.SignatureDigestMethod.ValueString()) diags = resp.State.Set(ctx, &result) resp.Diagnostics.Append(diags...) } @@ -292,7 +292,7 @@ func (r *resourceTFESAMLSettings) Update(ctx context.Context, req resource.Updat } // Delete disables the SAML Settings and then removes the resource from the state file. You cannot delete TFE SAML Settings, only disable them -func (r resourceTFESAMLSettings) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +func (r *resourceTFESAMLSettings) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var m modelTFESAMLSettings diags := req.State.Get(ctx, &m) resp.Diagnostics.Append(diags...) @@ -309,9 +309,23 @@ func (r resourceTFESAMLSettings) Delete(ctx context.Context, req resource.Delete } } +// ImportState implements resource.ResourceWithImportState +func (r *resourceTFESAMLSettings) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + samlSettings, err := r.client.Admin.Settings.SAML.Read(ctx) + if err != nil { + resp.Diagnostics.AddError("Error importing SAML Settings", "Could not retrieve SAML Settings, unexpected error: "+err.Error()) + return + } + + result := modelFromTFEAdminSAMLSettings(*samlSettings, signatureMethodSHA256, signatureMethodSHA256) + diags := resp.State.Set(ctx, &result) + resp.Diagnostics.Append(diags...) +} + var ( - _ resource.Resource = &resourceTFESAMLSettings{} - _ resource.ResourceWithConfigure = &resourceTFESAMLSettings{} + _ resource.Resource = &resourceTFESAMLSettings{} + _ resource.ResourceWithConfigure = &resourceTFESAMLSettings{} + _ resource.ResourceWithImportState = &resourceTFESAMLSettings{} ) // NewSAMLSettingsResource is a resource function for the framework provider. From 15bfe60ddb06d8cbc20f4ae2fbfcdb46d8d766e0 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Mon, 17 Jul 2023 10:26:58 +0300 Subject: [PATCH 07/19] feat(saml_settings): add PrivateKey, SignatureSigningMethod, SignatureDigestMethod fields in data source --- tfe/data_source_saml_settings.go | 15 +++++++++++++ tfe/resource_tfe_saml_settings.go | 26 ---------------------- website/docs/d/saml_settings.html.markdown | 4 ++++ 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/tfe/data_source_saml_settings.go b/tfe/data_source_saml_settings.go index e219e0eca..93b260188 100644 --- a/tfe/data_source_saml_settings.go +++ b/tfe/data_source_saml_settings.go @@ -49,6 +49,9 @@ type modelTFESAMLSettings struct { ACSConsumerURL types.String `tfsdk:"acs_consumer_url"` MetadataURL types.String `tfsdk:"metadata_url"` Certificate types.String `tfsdk:"certificate"` + PrivateKey types.String `tfsdk:"private_key"` + SignatureSigningMethod types.String `tfsdk:"signature_signing_method"` + SignatureDigestMethod types.String `tfsdk:"signature_digest_method"` } // Metadata returns the data source type name. @@ -114,6 +117,15 @@ func (d *dataSourceTFESAMLSettings) Schema(_ context.Context, _ datasource.Schem "certificate": schema.StringAttribute{ Computed: true, }, + "private_key": schema.StringAttribute{ + Computed: true, + }, + "signature_signing_method": schema.StringAttribute{ + Computed: true, + }, + "signature_digest_method": schema.StringAttribute{ + Computed: true, + }, }, } } @@ -164,6 +176,9 @@ func (d *dataSourceTFESAMLSettings) Read(ctx context.Context, _ datasource.ReadR ACSConsumerURL: types.StringValue(s.ACSConsumerURL), MetadataURL: types.StringValue(s.MetadataURL), Certificate: types.StringValue(s.Certificate), + PrivateKey: types.StringValue(s.PrivateKey), + SignatureSigningMethod: types.StringValue(s.SignatureSigningMethod), + SignatureDigestMethod: types.StringValue(s.SignatureDigestMethod), }) resp.Diagnostics.Append(diags...) } diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index d75c13f96..8d6ba899a 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -27,32 +27,6 @@ type resourceTFESAMLSettings struct { client *tfe.Client } -// modelTFESAMLSettings maps the resource schema data to a struct -type modelTFESAMLSettings struct { - ID types.String `tfsdk:"id"` - Enabled types.Bool `tfsdk:"enabled"` - Debug types.Bool `tfsdk:"debug"` - AuthnRequestsSigned types.Bool `tfsdk:"authn_requests_signed"` - WantAssertionsSigned types.Bool `tfsdk:"want_assertions_signed"` - TeamManagementEnabled types.Bool `tfsdk:"team_management_enabled"` - OldIDPCert types.String `tfsdk:"old_idp_cert"` - IDPCert types.String `tfsdk:"idp_cert"` - SLOEndpointURL types.String `tfsdk:"slo_endpoint_url"` - SSOEndpointURL types.String `tfsdk:"sso_endpoint_url"` - AttrUsername types.String `tfsdk:"attr_username"` - AttrGroups types.String `tfsdk:"attr_groups"` - AttrSiteAdmin types.String `tfsdk:"attr_site_admin"` - SiteAdminRole types.String `tfsdk:"site_admin_role"` - SSOAPITokenSessionTimeout types.Int64 `tfsdk:"sso_api_token_session_timeout"` - ACSConsumerURL types.String `tfsdk:"acs_consumer_url"` - MetadataURL types.String `tfsdk:"metadata_url"` - Certificate types.String `tfsdk:"certificate"` - PrivateKey types.String `tfsdk:"private_key"` - SignatureSigningMethod types.String `tfsdk:"signature_signing_method"` - SignatureDigestMethod types.String `tfsdk:"signature_digest_method"` - LastUpdated types.String `tfsdk:"last_updated"` -} - // modelFromTFEAdminSAMLSettings builds a modelTFESAMLSettings struct from a tfe.AdminSAMLSetting value func modelFromTFEAdminSAMLSettings(v tfe.AdminSAMLSetting, signatureSigningMethod, signatureDigestMethod string) modelTFESAMLSettings { return modelTFESAMLSettings{ diff --git a/website/docs/d/saml_settings.html.markdown b/website/docs/d/saml_settings.html.markdown index 9844165b6..2e6b83936 100644 --- a/website/docs/d/saml_settings.html.markdown +++ b/website/docs/d/saml_settings.html.markdown @@ -57,3 +57,7 @@ The following attributes are exported: * `acs_consumer_url` - ACS Consumer (Recipient) URL. * `metadata_url` - Metadata (Audience) URL. * `certificate` - Request and assertion signing certificate. +* `certificate` - Request and assertion signing certificate. +* `private_key` - The private key used for request and assertion signing. +* `signature_signing_method` - Signature Signing Method. +* `signature_digest_method` - Signature Digest Method. From fe8c46c89cbd91a749a5134b3a816c03ad3d543e Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Mon, 17 Jul 2023 10:56:00 +0300 Subject: [PATCH 08/19] feat(saml_settings): add Certificate, PrivateKey, TeamManagementEnabled to updateSAMLSettings --- tfe/resource_tfe_saml_settings.go | 43 ++++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index 8d6ba899a..d662893e6 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -3,8 +3,6 @@ package tfe import ( "context" "fmt" - "time" - tfe "github.com/hashicorp/go-tfe" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -28,8 +26,8 @@ type resourceTFESAMLSettings struct { } // modelFromTFEAdminSAMLSettings builds a modelTFESAMLSettings struct from a tfe.AdminSAMLSetting value -func modelFromTFEAdminSAMLSettings(v tfe.AdminSAMLSetting, signatureSigningMethod, signatureDigestMethod string) modelTFESAMLSettings { - return modelTFESAMLSettings{ +func modelFromTFEAdminSAMLSettings(v tfe.AdminSAMLSetting, privateKey types.String) modelTFESAMLSettings { + m := modelTFESAMLSettings{ ID: types.StringValue(v.ID), Enabled: types.BoolValue(v.Enabled), Debug: types.BoolValue(v.Debug), @@ -48,10 +46,14 @@ func modelFromTFEAdminSAMLSettings(v tfe.AdminSAMLSetting, signatureSigningMetho ACSConsumerURL: types.StringValue(v.ACSConsumerURL), MetadataURL: types.StringValue(v.MetadataURL), Certificate: types.StringValue(v.Certificate), - PrivateKey: types.StringValue(v.PrivateKey), - SignatureSigningMethod: types.StringValue(signatureSigningMethod), - SignatureDigestMethod: types.StringValue(signatureDigestMethod), + PrivateKey: types.StringNull(), + SignatureSigningMethod: types.StringValue(v.SignatureSigningMethod), + SignatureDigestMethod: types.StringValue(v.SignatureDigestMethod), + } + if privateKey.String() != "" { + m.PrivateKey = privateKey } + return m } // Configure implements resource.ResourceWithConfigure @@ -78,6 +80,7 @@ func (r *resourceTFESAMLSettings) Metadata(_ context.Context, req resource.Metad // Schema implements resource.Resource func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ + Version: 1, Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, @@ -165,15 +168,18 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem }, "certificate": schema.StringAttribute{ Description: "The certificate used for request and assertion signing", + Optional: true, Computed: true, }, "private_key": schema.StringAttribute{ Description: "The private key used for request and assertion signing", + Optional: true, Computed: true, Sensitive: true, }, "signature_signing_method": schema.StringAttribute{ Description: fmt.Sprintf("Signature Signing Method. Must be either `%s` or `%s`. Defaults to `%s`", signatureMethodSHA1, signatureMethodSHA256, signatureMethodSHA256), + Optional: true, Computed: true, Default: stringdefault.StaticString(signatureMethodSHA256), Validators: []validator.String{ @@ -185,6 +191,7 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem }, "signature_digest_method": schema.StringAttribute{ Description: fmt.Sprintf("Signature Digest Method. Must be either `%s` or `%s`. Defaults to `%s`", signatureMethodSHA1, signatureMethodSHA256, signatureMethodSHA256), + Optional: true, Computed: true, Default: stringdefault.StaticString(signatureMethodSHA256), Validators: []validator.String{ @@ -194,12 +201,7 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem ), }, }, - "last_updated": schema.StringAttribute{ - Description: "Time when resource was last updated", - Computed: true, - }, }, - Version: 1, } } @@ -218,7 +220,7 @@ func (r *resourceTFESAMLSettings) Read(ctx context.Context, req resource.ReadReq return } - result := modelFromTFEAdminSAMLSettings(*samlSettings, m.SignatureSigningMethod.ValueString(), m.SignatureDigestMethod.ValueString()) + result := modelFromTFEAdminSAMLSettings(*samlSettings, m.PrivateKey) diags = resp.State.Set(ctx, &result) resp.Diagnostics.Append(diags...) } @@ -238,7 +240,7 @@ func (r *resourceTFESAMLSettings) Create(ctx context.Context, req resource.Creat return } - result := modelFromTFEAdminSAMLSettings(*samlSettings, m.SignatureSigningMethod.ValueString(), m.SignatureDigestMethod.ValueString()) + result := modelFromTFEAdminSAMLSettings(*samlSettings, m.PrivateKey) diags = resp.State.Set(ctx, &result) resp.Diagnostics.Append(diags...) } @@ -258,9 +260,7 @@ func (r *resourceTFESAMLSettings) Update(ctx context.Context, req resource.Updat return } - result := modelFromTFEAdminSAMLSettings(*samlSettings, m.SignatureSigningMethod.ValueString(), m.SignatureDigestMethod.ValueString()) - result.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) - + result := modelFromTFEAdminSAMLSettings(*samlSettings, m.PrivateKey) diags = resp.State.Set(ctx, &result) resp.Diagnostics.Append(diags...) } @@ -291,7 +291,7 @@ func (r *resourceTFESAMLSettings) ImportState(ctx context.Context, req resource. return } - result := modelFromTFEAdminSAMLSettings(*samlSettings, signatureMethodSHA256, signatureMethodSHA256) + result := modelFromTFEAdminSAMLSettings(*samlSettings, types.StringValue("")) diags := resp.State.Set(ctx, &result) resp.Diagnostics.Append(diags...) } @@ -313,6 +313,8 @@ func (r *resourceTFESAMLSettings) updateSAMLSettings(ctx context.Context, m mode Enabled: basetypes.NewBoolValue(true).ValueBoolPointer(), Debug: m.Debug.ValueBoolPointer(), IDPCert: m.IDPCert.ValueStringPointer(), + Certificate: m.Certificate.ValueStringPointer(), + PrivateKey: m.Certificate.ValueStringPointer(), SLOEndpointURL: m.SLOEndpointURL.ValueStringPointer(), SSOEndpointURL: m.SSOEndpointURL.ValueStringPointer(), AttrUsername: m.AttrUsername.ValueStringPointer(), @@ -320,5 +322,10 @@ func (r *resourceTFESAMLSettings) updateSAMLSettings(ctx context.Context, m mode AttrSiteAdmin: m.AttrSiteAdmin.ValueStringPointer(), SiteAdminRole: m.SiteAdminRole.ValueStringPointer(), SSOAPITokenSessionTimeout: tfe.Int(int(m.SSOAPITokenSessionTimeout.ValueInt64())), + TeamManagementEnabled: m.TeamManagementEnabled.ValueBoolPointer(), + AuthnRequestsSigned: m.AuthnRequestsSigned.ValueBoolPointer(), + WantAssertionsSigned: m.WantAssertionsSigned.ValueBoolPointer(), + SignatureSigningMethod: m.SignatureSigningMethod.ValueStringPointer(), + SignatureDigestMethod: m.SignatureDigestMethod.ValueStringPointer(), }) } From cae71408a04d35b5874dc7e5b7562777eabcb3cf Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Tue, 18 Jul 2023 15:33:09 +0300 Subject: [PATCH 09/19] feat(r saml_settings): add basic test for resource --- tfe/resource_tfe_saml_settings.go | 75 ++++++++++++++++++-------- tfe/resource_tfe_saml_settings_test.go | 56 +++++++++++++++++++ 2 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 tfe/resource_tfe_saml_settings_test.go diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index d662893e6..1f84ea798 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -3,6 +3,7 @@ package tfe import ( "context" "fmt" + tfe "github.com/hashicorp/go-tfe" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -12,12 +13,17 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" ) const ( - signatureMethodSHA1 string = "SHA1" - signatureMethodSHA256 string = "SHA256" - defaultSSOAPITokenSessionTimeoutSeconds int64 = 1209600 // 14 days + samlSignatureMethodSHA1 string = "SHA1" + samlSignatureMethodSHA256 string = "SHA256" + samlDefaultAttrUsername string = "Username" + samlDefaultAttrSiteAdmin string = "SiteAdmin" + samlDefaultAttrGroups string = "MemberOf" + samlDefaultSiteAdminRole string = "site-admins" + samlDefaultSSOAPITokenSessionTimeoutSeconds int64 = 1209600 // 14 days ) // resourceTFESAMLSettings implements the tfe_saml_settings resource type @@ -46,11 +52,11 @@ func modelFromTFEAdminSAMLSettings(v tfe.AdminSAMLSetting, privateKey types.Stri ACSConsumerURL: types.StringValue(v.ACSConsumerURL), MetadataURL: types.StringValue(v.MetadataURL), Certificate: types.StringValue(v.Certificate), - PrivateKey: types.StringNull(), + PrivateKey: types.StringValue(""), SignatureSigningMethod: types.StringValue(v.SignatureSigningMethod), SignatureDigestMethod: types.StringValue(v.SignatureDigestMethod), } - if privateKey.String() != "" { + if len(privateKey.String()) > 0 { m.PrivateKey = privateKey } return m @@ -132,31 +138,31 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem Description: "Username Attribute Name specifies the name of the SAML attribute that determines the user's username", Optional: true, Computed: true, - Default: stringdefault.StaticString("Username"), + Default: stringdefault.StaticString(samlDefaultAttrUsername), }, "attr_site_admin": schema.StringAttribute{ Description: "Specifies the role for site admin access. Overrides the \"Site Admin Role\" method", Optional: true, Computed: true, - Default: stringdefault.StaticString("SiteAdmin"), + Default: stringdefault.StaticString(samlDefaultAttrSiteAdmin), }, "attr_groups": schema.StringAttribute{ Description: "Team Attribute Name specifies the name of the SAML attribute that determines team membership", Optional: true, Computed: true, - Default: stringdefault.StaticString("MemberOf"), + Default: stringdefault.StaticString(samlDefaultAttrGroups), }, "site_admin_role": schema.StringAttribute{ Description: "Specifies the role for site admin access, provided in the list of roles sent in the Team Attribute Name attribute", Optional: true, Computed: true, - Default: stringdefault.StaticString("site-admins"), + Default: stringdefault.StaticString(samlDefaultSiteAdminRole), }, "sso_api_token_session_timeout": schema.Int64Attribute{ Description: "Specifies the Single Sign On session timeout in seconds. Defaults to 14 days", Optional: true, Computed: true, - Default: staticInt64(defaultSSOAPITokenSessionTimeoutSeconds), + Default: staticInt64(samlDefaultSSOAPITokenSessionTimeoutSeconds), }, "acs_consumer_url": schema.StringAttribute{ Description: "ACS Consumer (Recipient) URL", @@ -173,31 +179,32 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem }, "private_key": schema.StringAttribute{ Description: "The private key used for request and assertion signing", + Default: stringdefault.StaticString(""), Optional: true, Computed: true, Sensitive: true, }, "signature_signing_method": schema.StringAttribute{ - Description: fmt.Sprintf("Signature Signing Method. Must be either `%s` or `%s`. Defaults to `%s`", signatureMethodSHA1, signatureMethodSHA256, signatureMethodSHA256), + Description: fmt.Sprintf("Signature Signing Method. Must be either `%s` or `%s`. Defaults to `%s`", samlSignatureMethodSHA1, samlSignatureMethodSHA256, samlSignatureMethodSHA256), Optional: true, Computed: true, - Default: stringdefault.StaticString(signatureMethodSHA256), + Default: stringdefault.StaticString(samlSignatureMethodSHA256), Validators: []validator.String{ stringvalidator.OneOf( - signatureMethodSHA1, - signatureMethodSHA256, + samlSignatureMethodSHA1, + samlSignatureMethodSHA256, ), }, }, "signature_digest_method": schema.StringAttribute{ - Description: fmt.Sprintf("Signature Digest Method. Must be either `%s` or `%s`. Defaults to `%s`", signatureMethodSHA1, signatureMethodSHA256, signatureMethodSHA256), + Description: fmt.Sprintf("Signature Digest Method. Must be either `%s` or `%s`. Defaults to `%s`", samlSignatureMethodSHA1, samlSignatureMethodSHA256, samlSignatureMethodSHA256), Optional: true, Computed: true, - Default: stringdefault.StaticString(signatureMethodSHA256), + Default: stringdefault.StaticString(samlSignatureMethodSHA256), Validators: []validator.String{ stringvalidator.OneOf( - signatureMethodSHA1, - signatureMethodSHA256, + samlSignatureMethodSHA1, + samlSignatureMethodSHA256, ), }, }, @@ -234,6 +241,7 @@ func (r *resourceTFESAMLSettings) Create(ctx context.Context, req resource.Creat return } + tflog.Debug(ctx, "Create SAML Settings") samlSettings, err := r.updateSAMLSettings(ctx, m) if err != nil { resp.Diagnostics.AddError("Error creating SAML Settings", "Could not set SAML Settings, unexpected error: "+err.Error()) @@ -254,6 +262,7 @@ func (r *resourceTFESAMLSettings) Update(ctx context.Context, req resource.Updat return } + tflog.Debug(ctx, "Update SAML Settings") samlSettings, err := r.updateSAMLSettings(ctx, m) if err != nil { resp.Diagnostics.AddError("Error updating SAML Settings", "Could not set SAML Settings, unexpected error: "+err.Error()) @@ -274,8 +283,32 @@ func (r *resourceTFESAMLSettings) Delete(ctx context.Context, req resource.Delet return } - _, err := r.client.Admin.Settings.SAML.Update(ctx, tfe.AdminSAMLSettingsUpdateOptions{ - Enabled: basetypes.NewBoolValue(false).ValueBoolPointer(), + tflog.Debug(ctx, "Revoke IDP Certificate") + _, err := r.client.Admin.Settings.SAML.RevokeIdpCert(ctx) + if err != nil { + resp.Diagnostics.AddError("Error revoking IDP Certificate", "Could not revoke IDP Cert, unexpected error: "+err.Error()) + return + } + + tflog.Debug(ctx, "Delete SAML Settings") + _, err = r.client.Admin.Settings.SAML.Update(ctx, tfe.AdminSAMLSettingsUpdateOptions{ + Enabled: basetypes.NewBoolValue(false).ValueBoolPointer(), + Debug: basetypes.NewBoolValue(false).ValueBoolPointer(), + AuthnRequestsSigned: basetypes.NewBoolValue(false).ValueBoolPointer(), + WantAssertionsSigned: basetypes.NewBoolValue(false).ValueBoolPointer(), + TeamManagementEnabled: basetypes.NewBoolValue(false).ValueBoolPointer(), + IDPCert: basetypes.NewStringNull().ValueStringPointer(), + SLOEndpointURL: basetypes.NewStringNull().ValueStringPointer(), + SSOEndpointURL: basetypes.NewStringNull().ValueStringPointer(), + AttrUsername: basetypes.NewStringValue(samlDefaultAttrUsername).ValueStringPointer(), + AttrSiteAdmin: basetypes.NewStringValue(samlDefaultAttrSiteAdmin).ValueStringPointer(), + AttrGroups: basetypes.NewStringValue(samlDefaultAttrGroups).ValueStringPointer(), + SiteAdminRole: basetypes.NewStringValue(samlDefaultSiteAdminRole).ValueStringPointer(), + SSOAPITokenSessionTimeout: tfe.Int(int(samlDefaultSSOAPITokenSessionTimeoutSeconds)), + Certificate: basetypes.NewStringNull().ValueStringPointer(), + PrivateKey: basetypes.NewStringNull().ValueStringPointer(), + SignatureSigningMethod: basetypes.NewStringValue(samlSignatureMethodSHA256).ValueStringPointer(), + SignatureDigestMethod: basetypes.NewStringValue(samlSignatureMethodSHA256).ValueStringPointer(), }) if err != nil { resp.Diagnostics.AddError("Error deleting SAML Settings", "Could not disable SAML Settings, unexpected error: "+err.Error()) @@ -314,7 +347,7 @@ func (r *resourceTFESAMLSettings) updateSAMLSettings(ctx context.Context, m mode Debug: m.Debug.ValueBoolPointer(), IDPCert: m.IDPCert.ValueStringPointer(), Certificate: m.Certificate.ValueStringPointer(), - PrivateKey: m.Certificate.ValueStringPointer(), + PrivateKey: m.PrivateKey.ValueStringPointer(), SLOEndpointURL: m.SLOEndpointURL.ValueStringPointer(), SSOEndpointURL: m.SSOEndpointURL.ValueStringPointer(), AttrUsername: m.AttrUsername.ValueStringPointer(), diff --git a/tfe/resource_tfe_saml_settings_test.go b/tfe/resource_tfe_saml_settings_test.go new file mode 100644 index 000000000..e2ebf083a --- /dev/null +++ b/tfe/resource_tfe_saml_settings_test.go @@ -0,0 +1,56 @@ +package tfe + +import ( + "fmt" + "github.com/hashicorp/go-tfe" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "strconv" + "testing" +) + +const testResourceName = "tfe_saml_settings.foobar" + +func TestAccTFESAMLSettings_basic(t *testing.T) { + s := tfe.AdminSAMLSetting{ + IDPCert: "testIDPCert", + SLOEndpointURL: "https://foobar.com/slo_endpoint_url", + SSOEndpointURL: "https://foobar.com/sso_endpoint_url", + } + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccMuxedProviders, + Steps: []resource.TestStep{ + { + Config: testAccTFESAMLSettings_basic(s), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testResourceName, "enabled", "true"), + resource.TestCheckResourceAttr(testResourceName, "debug", "false"), + resource.TestCheckResourceAttr(testResourceName, "authn_requests_signed", "false"), + resource.TestCheckResourceAttr(testResourceName, "want_assertions_signed", "false"), + resource.TestCheckResourceAttr(testResourceName, "team_management_enabled", "false"), + resource.TestCheckResourceAttr(testResourceName, "idp_cert", s.IDPCert), + resource.TestCheckResourceAttr(testResourceName, "slo_endpoint_url", s.SLOEndpointURL), + resource.TestCheckResourceAttr(testResourceName, "sso_endpoint_url", s.SSOEndpointURL), + resource.TestCheckResourceAttr(testResourceName, "attr_username", samlDefaultAttrUsername), + resource.TestCheckResourceAttr(testResourceName, "attr_site_admin", samlDefaultAttrSiteAdmin), + resource.TestCheckResourceAttr(testResourceName, "attr_groups", samlDefaultAttrGroups), + resource.TestCheckResourceAttr(testResourceName, "site_admin_role", samlDefaultSiteAdminRole), + resource.TestCheckResourceAttr(testResourceName, "sso_api_token_session_timeout", strconv.Itoa(int(samlDefaultSSOAPITokenSessionTimeoutSeconds))), + resource.TestCheckResourceAttrSet(testResourceName, "acs_consumer_url"), + resource.TestCheckResourceAttrSet(testResourceName, "metadata_url"), + resource.TestCheckResourceAttr(testResourceName, "signature_signing_method", samlSignatureMethodSHA256), + resource.TestCheckResourceAttr(testResourceName, "signature_digest_method", samlSignatureMethodSHA256), + ), + }, + }, + }) +} + +func testAccTFESAMLSettings_basic(s tfe.AdminSAMLSetting) string { + return fmt.Sprintf(` +resource "tfe_saml_settings" "foobar" { + idp_cert = "%s" + slo_endpoint_url = "%s" + sso_endpoint_url = "%s" +}`, s.IDPCert, s.SLOEndpointURL, s.SSOEndpointURL) +} From b5f6560381964f4c257565a9aaa43bbe893d7149 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Tue, 18 Jul 2023 17:11:49 +0300 Subject: [PATCH 10/19] feat(r saml_settings): add full and update test for resource --- tfe/resource_tfe_saml_settings_test.go | 150 ++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/tfe/resource_tfe_saml_settings_test.go b/tfe/resource_tfe_saml_settings_test.go index e2ebf083a..61ef4a831 100644 --- a/tfe/resource_tfe_saml_settings_test.go +++ b/tfe/resource_tfe_saml_settings_test.go @@ -12,7 +12,7 @@ const testResourceName = "tfe_saml_settings.foobar" func TestAccTFESAMLSettings_basic(t *testing.T) { s := tfe.AdminSAMLSetting{ - IDPCert: "testIDPCert", + IDPCert: "testIDPCertBasic", SLOEndpointURL: "https://foobar.com/slo_endpoint_url", SSOEndpointURL: "https://foobar.com/sso_endpoint_url", } @@ -46,6 +46,132 @@ func TestAccTFESAMLSettings_basic(t *testing.T) { }) } +func TestAccTFESAMLSettings_full(t *testing.T) { + s := tfe.AdminSAMLSetting{ + IDPCert: "testIDPCertFull", + SLOEndpointURL: "https://foobar.com/slo_endpoint_url", + SSOEndpointURL: "https://foobar.com/sso_endpoint_url", + Debug: true, + AuthnRequestsSigned: true, + WantAssertionsSigned: true, + TeamManagementEnabled: false, + AttrUsername: "Foo" + samlDefaultAttrUsername, + AttrSiteAdmin: "Foo" + samlDefaultAttrSiteAdmin, + AttrGroups: "Foo" + samlDefaultAttrGroups, + SiteAdminRole: "foo-" + samlDefaultSiteAdminRole, + SSOAPITokenSessionTimeout: 1101100, + Certificate: "TestCertificateFull", + PrivateKey: "TestPrivateKeyFull", + SignatureSigningMethod: samlSignatureMethodSHA1, + SignatureDigestMethod: samlSignatureMethodSHA256, + } + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccMuxedProviders, + Steps: []resource.TestStep{ + { + Config: testAccTFESAMLSettings_full(s), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testResourceName, "enabled", "true"), + resource.TestCheckResourceAttr(testResourceName, "debug", strconv.FormatBool(s.Debug)), + resource.TestCheckResourceAttr(testResourceName, "authn_requests_signed", strconv.FormatBool(s.AuthnRequestsSigned)), + resource.TestCheckResourceAttr(testResourceName, "want_assertions_signed", strconv.FormatBool(s.WantAssertionsSigned)), + resource.TestCheckResourceAttr(testResourceName, "team_management_enabled", strconv.FormatBool(s.TeamManagementEnabled)), + resource.TestCheckResourceAttr(testResourceName, "idp_cert", s.IDPCert), + resource.TestCheckResourceAttr(testResourceName, "slo_endpoint_url", s.SLOEndpointURL), + resource.TestCheckResourceAttr(testResourceName, "sso_endpoint_url", s.SSOEndpointURL), + resource.TestCheckResourceAttr(testResourceName, "attr_username", s.AttrUsername), + resource.TestCheckResourceAttr(testResourceName, "attr_site_admin", s.AttrSiteAdmin), + resource.TestCheckResourceAttr(testResourceName, "attr_groups", s.AttrGroups), + resource.TestCheckResourceAttr(testResourceName, "site_admin_role", s.SiteAdminRole), + resource.TestCheckResourceAttr(testResourceName, "sso_api_token_session_timeout", strconv.Itoa(s.SSOAPITokenSessionTimeout)), + resource.TestCheckResourceAttrSet(testResourceName, "acs_consumer_url"), + resource.TestCheckResourceAttrSet(testResourceName, "metadata_url"), + resource.TestCheckResourceAttr(testResourceName, "signature_signing_method", s.SignatureSigningMethod), + resource.TestCheckResourceAttr(testResourceName, "signature_digest_method", s.SignatureDigestMethod), + ), + }, + }, + }) +} + +func TestAccTFESAMLSettings_update(t *testing.T) { + s := tfe.AdminSAMLSetting{ + IDPCert: "testIDPCertUpdateInit", + SLOEndpointURL: "https://foobar.com/slo_endpoint_url", + SSOEndpointURL: "https://foobar.com/sso_endpoint_url", + } + updatedSetting := tfe.AdminSAMLSetting{ + IDPCert: "testIDPCertUpdateInit", + SLOEndpointURL: "https://foobar-updated.com/slo_endpoint_url", + SSOEndpointURL: "https://foobar-updated.com/sso_endpoint_url", + Debug: true, + AuthnRequestsSigned: true, + WantAssertionsSigned: true, + TeamManagementEnabled: false, + AttrUsername: "FooUpdate" + samlDefaultAttrUsername, + AttrSiteAdmin: "FooUpdate" + samlDefaultAttrSiteAdmin, + AttrGroups: "FooUpdate" + samlDefaultAttrGroups, + SiteAdminRole: "foo-update-" + samlDefaultSiteAdminRole, + SSOAPITokenSessionTimeout: 1234567, + Certificate: "TestCertificateUpdate", + PrivateKey: "TestPrivateKeyUpdate", + SignatureSigningMethod: samlSignatureMethodSHA1, + SignatureDigestMethod: samlSignatureMethodSHA256, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccMuxedProviders, + Steps: []resource.TestStep{ + { + Config: testAccTFESAMLSettings_basic(s), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testResourceName, "enabled", "true"), + resource.TestCheckResourceAttr(testResourceName, "debug", "false"), + resource.TestCheckResourceAttr(testResourceName, "authn_requests_signed", "false"), + resource.TestCheckResourceAttr(testResourceName, "want_assertions_signed", "false"), + resource.TestCheckResourceAttr(testResourceName, "team_management_enabled", "false"), + resource.TestCheckResourceAttr(testResourceName, "idp_cert", s.IDPCert), + resource.TestCheckResourceAttr(testResourceName, "slo_endpoint_url", s.SLOEndpointURL), + resource.TestCheckResourceAttr(testResourceName, "sso_endpoint_url", s.SSOEndpointURL), + resource.TestCheckResourceAttr(testResourceName, "attr_username", samlDefaultAttrUsername), + resource.TestCheckResourceAttr(testResourceName, "attr_site_admin", samlDefaultAttrSiteAdmin), + resource.TestCheckResourceAttr(testResourceName, "attr_groups", samlDefaultAttrGroups), + resource.TestCheckResourceAttr(testResourceName, "site_admin_role", samlDefaultSiteAdminRole), + resource.TestCheckResourceAttr(testResourceName, "sso_api_token_session_timeout", strconv.Itoa(int(samlDefaultSSOAPITokenSessionTimeoutSeconds))), + resource.TestCheckResourceAttrSet(testResourceName, "acs_consumer_url"), + resource.TestCheckResourceAttrSet(testResourceName, "metadata_url"), + resource.TestCheckResourceAttr(testResourceName, "signature_signing_method", samlSignatureMethodSHA256), + resource.TestCheckResourceAttr(testResourceName, "signature_digest_method", samlSignatureMethodSHA256), + ), + }, + { + Config: testAccTFESAMLSettings_full(updatedSetting), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testResourceName, "enabled", "true"), + resource.TestCheckResourceAttr(testResourceName, "debug", strconv.FormatBool(updatedSetting.Debug)), + resource.TestCheckResourceAttr(testResourceName, "authn_requests_signed", strconv.FormatBool(updatedSetting.AuthnRequestsSigned)), + resource.TestCheckResourceAttr(testResourceName, "want_assertions_signed", strconv.FormatBool(updatedSetting.WantAssertionsSigned)), + resource.TestCheckResourceAttr(testResourceName, "team_management_enabled", strconv.FormatBool(updatedSetting.TeamManagementEnabled)), + resource.TestCheckResourceAttr(testResourceName, "idp_cert", updatedSetting.IDPCert), + resource.TestCheckResourceAttr(testResourceName, "slo_endpoint_url", updatedSetting.SLOEndpointURL), + resource.TestCheckResourceAttr(testResourceName, "sso_endpoint_url", updatedSetting.SSOEndpointURL), + resource.TestCheckResourceAttr(testResourceName, "attr_username", updatedSetting.AttrUsername), + resource.TestCheckResourceAttr(testResourceName, "attr_site_admin", updatedSetting.AttrSiteAdmin), + resource.TestCheckResourceAttr(testResourceName, "attr_groups", updatedSetting.AttrGroups), + resource.TestCheckResourceAttr(testResourceName, "site_admin_role", updatedSetting.SiteAdminRole), + resource.TestCheckResourceAttr(testResourceName, "sso_api_token_session_timeout", strconv.Itoa(updatedSetting.SSOAPITokenSessionTimeout)), + resource.TestCheckResourceAttrSet(testResourceName, "acs_consumer_url"), + resource.TestCheckResourceAttrSet(testResourceName, "metadata_url"), + resource.TestCheckResourceAttr(testResourceName, "signature_signing_method", updatedSetting.SignatureSigningMethod), + resource.TestCheckResourceAttr(testResourceName, "signature_digest_method", updatedSetting.SignatureDigestMethod), + ), + }, + }, + }) +} + func testAccTFESAMLSettings_basic(s tfe.AdminSAMLSetting) string { return fmt.Sprintf(` resource "tfe_saml_settings" "foobar" { @@ -54,3 +180,25 @@ resource "tfe_saml_settings" "foobar" { sso_endpoint_url = "%s" }`, s.IDPCert, s.SLOEndpointURL, s.SSOEndpointURL) } + +func testAccTFESAMLSettings_full(s tfe.AdminSAMLSetting) string { + return fmt.Sprintf(` +resource "tfe_saml_settings" "foobar" { + idp_cert = "%s" + slo_endpoint_url = "%s" + sso_endpoint_url = "%s" + debug = %t + authn_requests_signed = %t + want_assertions_signed = %t + team_management_enabled = %t + attr_username = "%s" + attr_site_admin = "%s" + attr_groups = "%s" + site_admin_role = "%s" + sso_api_token_session_timeout = %d + certificate = "%s" + private_key = "%s" + signature_signing_method = "%s" + signature_digest_method = "%s" +}`, s.IDPCert, s.SLOEndpointURL, s.SSOEndpointURL, s.Debug, s.AuthnRequestsSigned, s.WantAssertionsSigned, s.TeamManagementEnabled, s.AttrUsername, s.AttrSiteAdmin, s.AttrGroups, s.SiteAdminRole, s.SSOAPITokenSessionTimeout, s.Certificate, s.PrivateKey, s.SignatureSigningMethod, s.SignatureDigestMethod) +} From 6accf422be77e9bcbb00f7d08c2bcdfa2b49477c Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Tue, 18 Jul 2023 17:19:21 +0300 Subject: [PATCH 11/19] feat(r saml_settings): add import test for resource --- tfe/data_source_saml_settings_test.go | 3 +-- tfe/resource_tfe_saml_settings_test.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/tfe/data_source_saml_settings_test.go b/tfe/data_source_saml_settings_test.go index 111e1d7f5..d859dc9d0 100644 --- a/tfe/data_source_saml_settings_test.go +++ b/tfe/data_source_saml_settings_test.go @@ -4,7 +4,6 @@ package tfe import ( - "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "testing" ) @@ -39,5 +38,5 @@ func TestAccTFESAMLSettingsDataSource_basic(t *testing.T) { } func testAccTFESAMLSettingsDataSourceConfig_basic() string { - return fmt.Sprint(`data "tfe_saml_settings" "foobar" {}`) + return `data "tfe_saml_settings" "foobar" {}` } diff --git a/tfe/resource_tfe_saml_settings_test.go b/tfe/resource_tfe_saml_settings_test.go index 61ef4a831..5f925662a 100644 --- a/tfe/resource_tfe_saml_settings_test.go +++ b/tfe/resource_tfe_saml_settings_test.go @@ -172,6 +172,28 @@ func TestAccTFESAMLSettings_update(t *testing.T) { }) } +func TestAccTFESAMLSettings_import(t *testing.T) { + s := tfe.AdminSAMLSetting{ + IDPCert: "testIDPCertImport", + SLOEndpointURL: "https://foobar.com/slo_endpoint_url", + SSOEndpointURL: "https://foobar.com/sso_endpoint_url", + } + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccMuxedProviders, + Steps: []resource.TestStep{ + { + Config: testAccTFESAMLSettings_basic(s), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccTFESAMLSettings_basic(s tfe.AdminSAMLSetting) string { return fmt.Sprintf(` resource "tfe_saml_settings" "foobar" { From 6db221a5461c7294e76f4c4ecae399f35a1942c9 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Tue, 18 Jul 2023 17:32:21 +0300 Subject: [PATCH 12/19] feat(r saml_settings): add markdown documentatione --- website/docs/r/saml_settings.html.markdown | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 website/docs/r/saml_settings.html.markdown diff --git a/website/docs/r/saml_settings.html.markdown b/website/docs/r/saml_settings.html.markdown new file mode 100644 index 000000000..93e2904fb --- /dev/null +++ b/website/docs/r/saml_settings.html.markdown @@ -0,0 +1,58 @@ +--- +layout: "tfe" +page_title: "Terraform Enterprise: tfe_saml_settings" +description: |- + Manages SAML Settings. +--- + +# tfe_saml_settings + +Creates, updates and destroys SAML Settings. + +## Example Usage + +Basic usage for SAML Settings: + +```hcl +resource "tfe_saml_settings" "this" { + idp_cert = "foobarCertificate" + slo_endpoint_url = "https://example.com/slo_endpoint_url" + sso_endpoint_url = "https://example.com/sso_endpoint_url" + } +``` + +## Argument Reference + +The following arguments are supported: + +* `idp_cert` - (Required) Identity Provider Certificate specifies the PEM encoded X.509 Certificate as provided by the IdP configuration. +* `slo_endpoint_url` - (Required) Single Log Out URL specifies the HTTPS endpoint on your IdP for single logout requests. This value is provided by the IdP configuration. +* `sso_endpoint_url` - (Required) Single Sign On URL specifies the HTTPS endpoint on your IdP for single sign-on requests. This value is provided by the IdP configuration. +* `debug` - (Optional) When sign-on fails and this is enabled, the SAMLResponse XML will be displayed on the login page. +* `authn_requests_signed` - (Optional) Whether to ensure that `` messages are signed. +* `want_assertions_signed` - (Optional) Whether to ensure that `` elements are signed. +* `team_management_enabled` - (Optional) Set it to false if you would rather use Terraform Enterprise to manage team membership. +* `attr_username` - (Optional) Username Attribute Name specifies the name of the SAML attribute that determines the user's username. +* `attr_site_admin` - (Optional) Specifies the role for site admin access. Overrides the `Site Admin Role` method. +* `attr_groups` - (Optional) Team Attribute Name specifies the name of the SAML attribute that determines team membership. +* `site_admin_role` - (Optional) Specifies the role for site admin access, provided in the list of roles sent in the Team Attribute Name attribute. +* `sso_api_token_session_timeout` - (Optional) Specifies the Single Sign On session timeout in seconds. Defaults to 14 days. +* `certificate` - (Optional) The certificate used for request and assertion signing. +* `private_key` - (Optional) The private key used for request and assertion signing. +* `signature_signing_method` - (Optional) Signature Signing Method. Must be either `SHA1` or `SHA256`. Defaults to `SHA256`. +* `signature_digest_method` - (Optional) Signature Digest Method. Must be either `SHA1` or `SHA256`. Defaults to `SHA256`. + +## Attributes Reference + +* `id` - The ID of the SAML Settings. Always `saml`. +* `acs_consumer_url` - ACS Consumer (Recipient) URL. +* `metadata_url` - Metadata (Audience) URL. +* `old_idp_cert` - Value of the old IDP Certificate. + +## Import + +SAML Settings can be imported. + +```shell +terraform import tfe_saml_settings.this saml +``` From b06d472f38629da0cc482405d06b74fca7b63fd8 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Tue, 18 Jul 2023 17:35:53 +0300 Subject: [PATCH 13/19] feat(r saml_settings): remove int64 from helpers --- tfe/resource_tfe_saml_settings.go | 3 ++- tfe/tool_helpers.go | 34 ------------------------------- 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index 1f84ea798..a6e162b8d 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" @@ -162,7 +163,7 @@ func (r *resourceTFESAMLSettings) Schema(ctx context.Context, req resource.Schem Description: "Specifies the Single Sign On session timeout in seconds. Defaults to 14 days", Optional: true, Computed: true, - Default: staticInt64(samlDefaultSSOAPITokenSessionTimeoutSeconds), + Default: int64default.StaticInt64(samlDefaultSSOAPITokenSessionTimeoutSeconds), }, "acs_consumer_url": schema.StringAttribute{ Description: "ACS Consumer (Recipient) URL", diff --git a/tfe/tool_helpers.go b/tfe/tool_helpers.go index f57835666..791395b56 100644 --- a/tfe/tool_helpers.go +++ b/tfe/tool_helpers.go @@ -4,11 +4,7 @@ package tfe import ( - "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" - "github.com/hashicorp/terraform-plugin-framework/types" - tfe "github.com/hashicorp/go-tfe" ) @@ -54,33 +50,3 @@ func fetchTerraformVersionID(version string, client *tfe.Client) (string, error) return "", fmt.Errorf("terraform version not found") } - -// staticInt64 returns a static Int64 value default handler. -// -// Use staticInt64 if a static default value for a Int64 should be set. -func staticInt64(defaultVal int64) defaults.Int64 { - return staticInt64Default{ - defaultVal: defaultVal, - } -} - -// staticStringDefault is static value default handler that -// sets a value on a string attribute. -type staticInt64Default struct { - defaultVal int64 -} - -// Description returns a human-readable description of the default value handler. -func (d staticInt64Default) Description(_ context.Context) string { - return fmt.Sprintf("value defaults to %d", d.defaultVal) -} - -// MarkdownDescription returns a markdown description of the default value handler. -func (d staticInt64Default) MarkdownDescription(_ context.Context) string { - return fmt.Sprintf("value defaults to `%d`", d.defaultVal) -} - -// DefaultInt64 implements the static default value logic. -func (d staticInt64Default) DefaultInt64(_ context.Context, req defaults.Int64Request, resp *defaults.Int64Response) { - resp.PlanValue = types.Int64Value(d.defaultVal) -} From 90848e6ecd9b928ee1e9dc6b10e9492b95af1707 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Wed, 19 Jul 2023 12:20:01 +0300 Subject: [PATCH 14/19] fix(d saml_settings): make private key sensitive --- tfe/data_source_saml_settings.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tfe/data_source_saml_settings.go b/tfe/data_source_saml_settings.go index 93b260188..662b5b71e 100644 --- a/tfe/data_source_saml_settings.go +++ b/tfe/data_source_saml_settings.go @@ -118,7 +118,8 @@ func (d *dataSourceTFESAMLSettings) Schema(_ context.Context, _ datasource.Schem Computed: true, }, "private_key": schema.StringAttribute{ - Computed: true, + Computed: true, + Sensitive: true, }, "signature_signing_method": schema.StringAttribute{ Computed: true, From 54bb12363a33ecb0aaea7d4a1dc90ce092e0a5cf Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Wed, 19 Jul 2023 13:21:20 +0300 Subject: [PATCH 15/19] feat(r saml_settings): add delete test check --- tfe/resource_tfe_saml_settings.go | 10 ++-- tfe/resource_tfe_saml_settings_test.go | 64 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index a6e162b8d..d9f2a38d1 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -298,16 +298,16 @@ func (r *resourceTFESAMLSettings) Delete(ctx context.Context, req resource.Delet AuthnRequestsSigned: basetypes.NewBoolValue(false).ValueBoolPointer(), WantAssertionsSigned: basetypes.NewBoolValue(false).ValueBoolPointer(), TeamManagementEnabled: basetypes.NewBoolValue(false).ValueBoolPointer(), - IDPCert: basetypes.NewStringNull().ValueStringPointer(), - SLOEndpointURL: basetypes.NewStringNull().ValueStringPointer(), - SSOEndpointURL: basetypes.NewStringNull().ValueStringPointer(), + IDPCert: basetypes.NewStringValue("").ValueStringPointer(), + SLOEndpointURL: basetypes.NewStringValue("").ValueStringPointer(), + SSOEndpointURL: basetypes.NewStringValue("").ValueStringPointer(), AttrUsername: basetypes.NewStringValue(samlDefaultAttrUsername).ValueStringPointer(), AttrSiteAdmin: basetypes.NewStringValue(samlDefaultAttrSiteAdmin).ValueStringPointer(), AttrGroups: basetypes.NewStringValue(samlDefaultAttrGroups).ValueStringPointer(), SiteAdminRole: basetypes.NewStringValue(samlDefaultSiteAdminRole).ValueStringPointer(), SSOAPITokenSessionTimeout: tfe.Int(int(samlDefaultSSOAPITokenSessionTimeoutSeconds)), - Certificate: basetypes.NewStringNull().ValueStringPointer(), - PrivateKey: basetypes.NewStringNull().ValueStringPointer(), + Certificate: basetypes.NewStringValue("").ValueStringPointer(), + PrivateKey: basetypes.NewStringValue("").ValueStringPointer(), SignatureSigningMethod: basetypes.NewStringValue(samlSignatureMethodSHA256).ValueStringPointer(), SignatureDigestMethod: basetypes.NewStringValue(samlSignatureMethodSHA256).ValueStringPointer(), }) diff --git a/tfe/resource_tfe_saml_settings_test.go b/tfe/resource_tfe_saml_settings_test.go index 5f925662a..882e5d2c8 100644 --- a/tfe/resource_tfe_saml_settings_test.go +++ b/tfe/resource_tfe_saml_settings_test.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/go-tfe" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "strconv" "testing" ) @@ -19,6 +20,7 @@ func TestAccTFESAMLSettings_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV5ProviderFactories: testAccMuxedProviders, + CheckDestroy: testAccTFESAMLSettingsDestroy, Steps: []resource.TestStep{ { Config: testAccTFESAMLSettings_basic(s), @@ -68,6 +70,7 @@ func TestAccTFESAMLSettings_full(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV5ProviderFactories: testAccMuxedProviders, + CheckDestroy: testAccTFESAMLSettingsDestroy, Steps: []resource.TestStep{ { Config: testAccTFESAMLSettings_full(s), @@ -123,6 +126,7 @@ func TestAccTFESAMLSettings_update(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV5ProviderFactories: testAccMuxedProviders, + CheckDestroy: testAccTFESAMLSettingsDestroy, Steps: []resource.TestStep{ { Config: testAccTFESAMLSettings_basic(s), @@ -181,6 +185,7 @@ func TestAccTFESAMLSettings_import(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV5ProviderFactories: testAccMuxedProviders, + CheckDestroy: testAccTFESAMLSettingsDestroy, Steps: []resource.TestStep{ { Config: testAccTFESAMLSettings_basic(s), @@ -194,6 +199,65 @@ func TestAccTFESAMLSettings_import(t *testing.T) { }) } +func testAccTFESAMLSettingsDestroy(_ *terraform.State) error { + s, err := testAccProvider.Meta().(ConfiguredClient).Client.Admin.Settings.SAML.Read(ctx) + if err != nil { + return err + } + if s.Enabled { + return fmt.Errorf("SAML settings are still enabled") + } + if s.Debug { + return fmt.Errorf("SAML settings debug is set to true") + } + if s.AuthnRequestsSigned { + return fmt.Errorf("SAML settings AuthnRequestsSigned is set to true") + } + if s.WantAssertionsSigned { + return fmt.Errorf("SAML settings WantAssertionsSigned is set to true") + } + if s.TeamManagementEnabled { + return fmt.Errorf("SAML settings TeamManagementEnabled is set to true") + } + if s.IDPCert != "" { + return fmt.Errorf("SAML settings IDPCert is not empty: `%s`", s.IDPCert) + } + if s.SLOEndpointURL != "" { + return fmt.Errorf("SAML settings SLOEndpointURL is not empty: `%s`", s.SLOEndpointURL) + } + if s.SSOEndpointURL != "" { + return fmt.Errorf("SAML settings SSOEndpointURL is not empty: `%s`", s.SSOEndpointURL) + } + if s.Certificate != "" { + return fmt.Errorf("SAML settings Certificate is not empty: `%s`", s.Certificate) + } + if s.PrivateKey != "" { + return fmt.Errorf("SAML settings PrivateKey is not empty") + } + if s.AttrUsername != samlDefaultAttrUsername { + return fmt.Errorf("SAML settings AttrUsername is not `%s`", samlDefaultAttrUsername) + } + if s.AttrSiteAdmin != samlDefaultAttrSiteAdmin { + return fmt.Errorf("SAML settings AttrSiteAdmin is not `%s`", samlDefaultAttrSiteAdmin) + } + if s.AttrGroups != samlDefaultAttrGroups { + return fmt.Errorf("SAML settings AttrGroups is not `%s`", samlDefaultAttrGroups) + } + if s.SiteAdminRole != samlDefaultSiteAdminRole { + return fmt.Errorf("SAML settings SiteAdminRole is not `%s`", samlDefaultSiteAdminRole) + } + if s.SignatureSigningMethod != samlSignatureMethodSHA256 { + return fmt.Errorf("SAML settings SignatureSigningMethod is not `%s`", samlSignatureMethodSHA256) + } + if s.SignatureDigestMethod != samlSignatureMethodSHA256 { + return fmt.Errorf("SAML settings SignatureDigestMethod is not `%s`", samlSignatureMethodSHA256) + } + if s.SSOAPITokenSessionTimeout != int(samlDefaultSSOAPITokenSessionTimeoutSeconds) { + return fmt.Errorf("SAML settings SignatureDigestMethod is not `%d`", samlDefaultSSOAPITokenSessionTimeoutSeconds) + } + return nil +} + func testAccTFESAMLSettings_basic(s tfe.AdminSAMLSetting) string { return fmt.Sprintf(` resource "tfe_saml_settings" "foobar" { From 10b282d771c90da9f9edbf72fd7e235ab872f69c Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Wed, 19 Jul 2023 15:04:00 +0300 Subject: [PATCH 16/19] docs(changelog): add features to changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74b755a3d..a8af1943c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## Unreleased + +FEATURES: +* **New Resource**: `r/tfe_saml_settings` manages SAML Settings, by @karvounis-form3 [970](https://github.com/hashicorp/terraform-provider-tfe/pull/970) +* `d/tfe_saml_settings`: Add PrivateKey (sensitive), SignatureSigningMethod, and SignatureDigestMethod attributes, by @karvounis-form3 [970](https://github.com/hashicorp/terraform-provider-tfe/pull/970) + +NOTES: +* The provider is now using go-tfe [v1.30.0](https://github.com/hashicorp/go-tfe/releases/tag/v1.30.0), by @karvounis-form3 [970](https://github.com/hashicorp/terraform-provider-tfe/pull/970) + ## v0.47.0 (July 18, 2023) FEATURES: From e31404e148305ab554f76f6956d43a4308496903 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Wed, 19 Jul 2023 15:12:57 +0300 Subject: [PATCH 17/19] fix(r saml_settings): wrap error returns --- tfe/resource_tfe_saml_settings.go | 6 +++++- tfe/resource_tfe_saml_settings_test.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index d9f2a38d1..3d340e07b 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -343,7 +343,7 @@ func NewSAMLSettingsResource() resource.Resource { // updateSAMLSettings was created to keep the code DRY. It is used in both Create and Update functions func (r *resourceTFESAMLSettings) updateSAMLSettings(ctx context.Context, m modelTFESAMLSettings) (*tfe.AdminSAMLSetting, error) { - return r.client.Admin.Settings.SAML.Update(ctx, tfe.AdminSAMLSettingsUpdateOptions{ + s, err := r.client.Admin.Settings.SAML.Update(ctx, tfe.AdminSAMLSettingsUpdateOptions{ Enabled: basetypes.NewBoolValue(true).ValueBoolPointer(), Debug: m.Debug.ValueBoolPointer(), IDPCert: m.IDPCert.ValueStringPointer(), @@ -362,4 +362,8 @@ func (r *resourceTFESAMLSettings) updateSAMLSettings(ctx context.Context, m mode SignatureSigningMethod: m.SignatureSigningMethod.ValueStringPointer(), SignatureDigestMethod: m.SignatureDigestMethod.ValueStringPointer(), }) + if err != nil { + return s, fmt.Errorf("failed to update SAML Settings %v", err) + } + return s, err } diff --git a/tfe/resource_tfe_saml_settings_test.go b/tfe/resource_tfe_saml_settings_test.go index 882e5d2c8..fb456bef6 100644 --- a/tfe/resource_tfe_saml_settings_test.go +++ b/tfe/resource_tfe_saml_settings_test.go @@ -202,7 +202,7 @@ func TestAccTFESAMLSettings_import(t *testing.T) { func testAccTFESAMLSettingsDestroy(_ *terraform.State) error { s, err := testAccProvider.Meta().(ConfiguredClient).Client.Admin.Settings.SAML.Read(ctx) if err != nil { - return err + return fmt.Errorf("failed to read SAML Settings: %v", err) } if s.Enabled { return fmt.Errorf("SAML settings are still enabled") From e8a6a33623fbd12a52082991dd7b6e694bdf6d6a Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Wed, 19 Jul 2023 19:38:58 +0300 Subject: [PATCH 18/19] fix(r saml_settings): remove RevokeIdpCert call when deleting --- tfe/resource_tfe_saml_settings.go | 9 +-------- website/docs/r/saml_settings.html.markdown | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/tfe/resource_tfe_saml_settings.go b/tfe/resource_tfe_saml_settings.go index 3d340e07b..e03a8301b 100644 --- a/tfe/resource_tfe_saml_settings.go +++ b/tfe/resource_tfe_saml_settings.go @@ -284,15 +284,8 @@ func (r *resourceTFESAMLSettings) Delete(ctx context.Context, req resource.Delet return } - tflog.Debug(ctx, "Revoke IDP Certificate") - _, err := r.client.Admin.Settings.SAML.RevokeIdpCert(ctx) - if err != nil { - resp.Diagnostics.AddError("Error revoking IDP Certificate", "Could not revoke IDP Cert, unexpected error: "+err.Error()) - return - } - tflog.Debug(ctx, "Delete SAML Settings") - _, err = r.client.Admin.Settings.SAML.Update(ctx, tfe.AdminSAMLSettingsUpdateOptions{ + _, err := r.client.Admin.Settings.SAML.Update(ctx, tfe.AdminSAMLSettingsUpdateOptions{ Enabled: basetypes.NewBoolValue(false).ValueBoolPointer(), Debug: basetypes.NewBoolValue(false).ValueBoolPointer(), AuthnRequestsSigned: basetypes.NewBoolValue(false).ValueBoolPointer(), diff --git a/website/docs/r/saml_settings.html.markdown b/website/docs/r/saml_settings.html.markdown index 93e2904fb..40c0764ce 100644 --- a/website/docs/r/saml_settings.html.markdown +++ b/website/docs/r/saml_settings.html.markdown @@ -7,7 +7,7 @@ description: |- # tfe_saml_settings -Creates, updates and destroys SAML Settings. +(TFE only) Creates, updates and destroys SAML Settings. ## Example Usage From 5d3608cf1f87767538d6e0e6cc4ce83a93e67383 Mon Sep 17 00:00:00 2001 From: Evangelos Karvounis Date: Thu, 20 Jul 2023 10:50:58 +0300 Subject: [PATCH 19/19] fix(r saml_settings): extended import test --- tfe/resource_tfe_saml_settings_test.go | 37 +++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/tfe/resource_tfe_saml_settings_test.go b/tfe/resource_tfe_saml_settings_test.go index fb456bef6..9a08206e5 100644 --- a/tfe/resource_tfe_saml_settings_test.go +++ b/tfe/resource_tfe_saml_settings_test.go @@ -177,10 +177,13 @@ func TestAccTFESAMLSettings_update(t *testing.T) { } func TestAccTFESAMLSettings_import(t *testing.T) { + idpCert := "testIDPCertImport" + slo := "https://foobar-import.com/slo_endpoint_url" + sso := "https://foobar-import.com/sso_endpoint_url" s := tfe.AdminSAMLSetting{ - IDPCert: "testIDPCertImport", - SLOEndpointURL: "https://foobar.com/slo_endpoint_url", - SSOEndpointURL: "https://foobar.com/sso_endpoint_url", + IDPCert: idpCert, + SLOEndpointURL: slo, + SSOEndpointURL: sso, } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -191,9 +194,31 @@ func TestAccTFESAMLSettings_import(t *testing.T) { Config: testAccTFESAMLSettings_basic(s), }, { - ResourceName: testResourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: testResourceName, + ImportState: true, + ImportStateCheck: func(s []*terraform.InstanceState) error { + if len(s) != 1 { + return fmt.Errorf("expected 1 state: %+v", s) + } + rs := s[0] + if rs.Attributes["private_key"] != "" { + return fmt.Errorf("expected private_key attribute to not be set, received: %s", rs.Attributes["private_key"]) + + } + if rs.Attributes["idp_cert"] != idpCert { + return fmt.Errorf("expected idp_cert attribute to be equal to %s, received: %s", idpCert, rs.Attributes["idp_cert"]) + + } + if rs.Attributes["slo_endpoint_url"] != slo { + return fmt.Errorf("expected slo_endpoint_url attribute to be equal to %s, received: %s", slo, rs.Attributes["slo_endpoint_url"]) + + } + if rs.Attributes["sso_endpoint_url"] != sso { + return fmt.Errorf("expected sso_endpoint_url attribute to be equal to %s, received: %s", sso, rs.Attributes["sso_endpoint_url"]) + + } + return nil + }, }, }, })