From 896499579c94ace771cc3512c2d51f10f8522548 Mon Sep 17 00:00:00 2001 From: Denis Mishin Date: Fri, 31 Jan 2025 15:40:22 -0500 Subject: [PATCH 1/3] add more route options --- example/main.tf | 35 +++++ internal/provider/route.go | 66 +++++++++ internal/provider/route_model.go | 175 +++++++++++++++++------ internal/provider/route_test.go | 237 +++++++++++++++++++++++++++++++ 4 files changed, 471 insertions(+), 42 deletions(-) diff --git a/example/main.tf b/example/main.tf index 5be073b..7dae9aa 100644 --- a/example/main.tf +++ b/example/main.tf @@ -203,6 +203,41 @@ resource "pomerium_route" "kubernetes_route" { tls_upstream_allow_renegotiation = true } +resource "pomerium_route" "advanced_route" { + name = "advanced-route" + from = "https://advanced.corp.example.com" + to = ["https://internal-service.example.com"] + namespace_id = pomerium_namespace.test_namespace.id + + # Response header manipulation + rewrite_response_headers = [ + { + header = "Location" + prefix = "http://internal" + value = "https://external" + }, + { + header = "Content-Security-Policy" + value = "default-src 'self'" + } + ] + set_response_headers = { + "Strict-Transport-Security" = "max-age=31536000" + "X-Frame-Options" = "DENY" + } + + tls_custom_ca_key_pair_id = pomerium_key_pair.test_key_pair.id + tls_skip_verify = false + + enable_google_cloud_serverless_authentication = true + kubernetes_service_account_token_file = "/path/to/token" + + description = "Advanced route with security headers" + logo_url = "https://example.com/logo.png" + + show_error_details = true +} + # Data source examples data "pomerium_namespaces" "all_namespaces" {} diff --git a/internal/provider/route.go b/internal/provider/route.go index 6c3e167..1be433a 100644 --- a/internal/provider/route.go +++ b/internal/provider/route.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + "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" @@ -199,6 +200,71 @@ func (r *RouteResource) Schema(_ context.Context, _ resource.SchemaRequest, resp Computed: true, }, "jwt_groups_filter": JWTGroupsFilterSchema, + "jwt_issuer_format": schema.ObjectAttribute{ + Description: "JWT issuer format configuration.", + Optional: true, + AttributeTypes: map[string]attr.Type{ + "format": types.StringType, + }, + }, + "rewrite_response_headers": schema.SetNestedAttribute{ + Description: "Response header rewrite rules.", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "header": schema.StringAttribute{ + Required: true, + Description: "Header name to rewrite", + }, + "prefix": schema.StringAttribute{ + Optional: true, + Description: "Prefix matcher for the header", + }, + "value": schema.StringAttribute{ + Required: true, + Description: "New value for the header", + }, + }, + }, + }, + "tls_custom_ca_key_pair_id": schema.StringAttribute{ + Description: "Custom CA key pair ID for TLS verification.", + Optional: true, + }, + "tls_client_key_pair_id": schema.StringAttribute{ + Description: "Client key pair ID for TLS client authentication.", + Optional: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the route.", + Optional: true, + }, + "kubernetes_service_account_token_file": schema.StringAttribute{ + Description: "Path to the Kubernetes service account token file.", + Optional: true, + }, + "logo_url": schema.StringAttribute{ + Description: "URL to the logo image.", + Optional: true, + }, + "enable_google_cloud_serverless_authentication": schema.BoolAttribute{ + Description: "Enable Google Cloud serverless authentication.", + Optional: true, + }, + "redirect": schema.ObjectAttribute{ + Description: "Redirect configuration.", + Optional: true, + AttributeTypes: map[string]attr.Type{ + "host_redirect": types.StringType, + "https_redirect": types.BoolType, + "path_redirect": types.StringType, + "prefix_rewrite": types.StringType, + "response_code": types.Int64Type, + "strip_query": types.BoolType, + "scheme_redirect": types.StringType, + "port_redirect": types.Int64Type, + }, + }, }, } } diff --git a/internal/provider/route_model.go b/internal/provider/route_model.go index 22b6c18..f47cfe6 100644 --- a/internal/provider/route_model.go +++ b/internal/provider/route_model.go @@ -4,6 +4,7 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/pomerium/enterprise-client-go/pb" @@ -11,42 +12,116 @@ import ( // RouteModel represents the shared model for route resources and data sources type RouteModel struct { - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - From types.String `tfsdk:"from"` - To types.Set `tfsdk:"to"` - NamespaceID types.String `tfsdk:"namespace_id"` - Policies types.Set `tfsdk:"policies"` - StatName types.String `tfsdk:"stat_name"` - Prefix types.String `tfsdk:"prefix"` - Path types.String `tfsdk:"path"` - Regex types.String `tfsdk:"regex"` - PrefixRewrite types.String `tfsdk:"prefix_rewrite"` - RegexRewritePattern types.String `tfsdk:"regex_rewrite_pattern"` - RegexRewriteSubstitution types.String `tfsdk:"regex_rewrite_substitution"` - HostRewrite types.String `tfsdk:"host_rewrite"` - HostRewriteHeader types.String `tfsdk:"host_rewrite_header"` - HostPathRegexRewritePattern types.String `tfsdk:"host_path_regex_rewrite_pattern"` - HostPathRegexRewriteSubstitution types.String `tfsdk:"host_path_regex_rewrite_substitution"` - RegexPriorityOrder types.Int64 `tfsdk:"regex_priority_order"` - Timeout timetypes.GoDuration `tfsdk:"timeout"` - IdleTimeout timetypes.GoDuration `tfsdk:"idle_timeout"` - AllowWebsockets types.Bool `tfsdk:"allow_websockets"` - AllowSPDY types.Bool `tfsdk:"allow_spdy"` - TLSSkipVerify types.Bool `tfsdk:"tls_skip_verify"` - TLSUpstreamServerName types.String `tfsdk:"tls_upstream_server_name"` - TLSDownstreamServerName types.String `tfsdk:"tls_downstream_server_name"` - TLSUpstreamAllowRenegotiation types.Bool `tfsdk:"tls_upstream_allow_renegotiation"` - SetRequestHeaders types.Map `tfsdk:"set_request_headers"` - RemoveRequestHeaders types.Set `tfsdk:"remove_request_headers"` - SetResponseHeaders types.Map `tfsdk:"set_response_headers"` - PreserveHostHeader types.Bool `tfsdk:"preserve_host_header"` - PassIdentityHeaders types.Bool `tfsdk:"pass_identity_headers"` - KubernetesServiceAccountToken types.String `tfsdk:"kubernetes_service_account_token"` - IDPClientID types.String `tfsdk:"idp_client_id"` - IDPClientSecret types.String `tfsdk:"idp_client_secret"` - ShowErrorDetails types.Bool `tfsdk:"show_error_details"` - JWTGroupsFilter types.Object `tfsdk:"jwt_groups_filter"` + AllowSPDY types.Bool `tfsdk:"allow_spdy"` + AllowWebsockets types.Bool `tfsdk:"allow_websockets"` + Description types.String `tfsdk:"description"` + EnableGoogleCloudServerlessAuthentication types.Bool `tfsdk:"enable_google_cloud_serverless_authentication"` + From types.String `tfsdk:"from"` + HostPathRegexRewritePattern types.String `tfsdk:"host_path_regex_rewrite_pattern"` + HostPathRegexRewriteSubstitution types.String `tfsdk:"host_path_regex_rewrite_substitution"` + HostRewrite types.String `tfsdk:"host_rewrite"` + HostRewriteHeader types.String `tfsdk:"host_rewrite_header"` + ID types.String `tfsdk:"id"` + IdleTimeout timetypes.GoDuration `tfsdk:"idle_timeout"` + IDPClientID types.String `tfsdk:"idp_client_id"` + IDPClientSecret types.String `tfsdk:"idp_client_secret"` + JWTGroupsFilter types.Object `tfsdk:"jwt_groups_filter"` + JWTIssuerFormat types.Object `tfsdk:"jwt_issuer_format"` + KubernetesServiceAccountToken types.String `tfsdk:"kubernetes_service_account_token"` + KubernetesServiceAccountTokenFile types.String `tfsdk:"kubernetes_service_account_token_file"` + LogoURL types.String `tfsdk:"logo_url"` + Name types.String `tfsdk:"name"` + NamespaceID types.String `tfsdk:"namespace_id"` + PassIdentityHeaders types.Bool `tfsdk:"pass_identity_headers"` + Path types.String `tfsdk:"path"` + Policies types.Set `tfsdk:"policies"` + Prefix types.String `tfsdk:"prefix"` + PrefixRewrite types.String `tfsdk:"prefix_rewrite"` + PreserveHostHeader types.Bool `tfsdk:"preserve_host_header"` + Redirect types.Object `tfsdk:"redirect"` + Regex types.String `tfsdk:"regex"` + RegexPriorityOrder types.Int64 `tfsdk:"regex_priority_order"` + RegexRewritePattern types.String `tfsdk:"regex_rewrite_pattern"` + RegexRewriteSubstitution types.String `tfsdk:"regex_rewrite_substitution"` + RemoveRequestHeaders types.Set `tfsdk:"remove_request_headers"` + RewriteResponseHeaders types.Set `tfsdk:"rewrite_response_headers"` + SetRequestHeaders types.Map `tfsdk:"set_request_headers"` + SetResponseHeaders types.Map `tfsdk:"set_response_headers"` + ShowErrorDetails types.Bool `tfsdk:"show_error_details"` + StatName types.String `tfsdk:"stat_name"` + Timeout timetypes.GoDuration `tfsdk:"timeout"` + TLSClientKeyPairID types.String `tfsdk:"tls_client_key_pair_id"` + TLSCustomCAKeyPairID types.String `tfsdk:"tls_custom_ca_key_pair_id"` + TLSDownstreamServerName types.String `tfsdk:"tls_downstream_server_name"` + TLSSkipVerify types.Bool `tfsdk:"tls_skip_verify"` + TLSUpstreamAllowRenegotiation types.Bool `tfsdk:"tls_upstream_allow_renegotiation"` + TLSUpstreamServerName types.String `tfsdk:"tls_upstream_server_name"` + To types.Set `tfsdk:"to"` +} + +var rewriteHeaderAttrTypes = map[string]attr.Type{ + "header": types.StringType, + "value": types.StringType, + "prefix": types.StringType, +} + +// RewriteHeaderAttrTypes returns the attribute type map for rewrite headers +func RewriteHeaderAttrTypes() map[string]attr.Type { + return rewriteHeaderAttrTypes +} + +func rewriteHeadersToPB(src types.Set) []*pb.RouteRewriteHeader { + if (src).IsNull() { + return nil + } + + headers := make([]*pb.RouteRewriteHeader, 0) + elements := src.Elements() + for _, element := range elements { + obj := element.(types.Object) + prefixAttr := obj.Attributes()["prefix"].(types.String) + + header := &pb.RouteRewriteHeader{ + Header: obj.Attributes()["header"].(types.String).ValueString(), + Value: obj.Attributes()["value"].(types.String).ValueString(), + } + + if !prefixAttr.IsNull() && prefixAttr.ValueString() != "" { + header.Matcher = &pb.RouteRewriteHeader_Prefix{Prefix: prefixAttr.ValueString()} + } + + headers = append(headers, header) + } + return headers +} + +func rewriteHeadersFromPB(headers []*pb.RouteRewriteHeader) types.Set { + if len(headers) == 0 { + return types.SetNull(RewriteHeaderObjectType()) + } + + elements := make([]attr.Value, 0, len(headers)) + for _, header := range headers { + prefix := header.GetPrefix() + prefixValue := types.StringNull() + if prefix != "" { + prefixValue = types.StringValue(prefix) + } + + attrs := map[string]attr.Value{ + "header": types.StringValue(header.Header), + "value": types.StringValue(header.Value), + "prefix": prefixValue, + } + obj, _ := types.ObjectValue(rewriteHeaderAttrTypes, attrs) + elements = append(elements, obj) + } + result, _ := types.SetValue(RewriteHeaderObjectType(), elements) + return result +} + +func RewriteHeaderObjectType() attr.Type { + return types.ObjectType{AttrTypes: rewriteHeaderAttrTypes} } func ConvertRouteToPB( @@ -90,14 +165,19 @@ func ConvertRouteToPB( pbRoute.IdpClientSecret = src.IDPClientSecret.ValueStringPointer() pbRoute.ShowErrorDetails = src.ShowErrorDetails.ValueBool() JWTGroupsFilterToPB(ctx, &pbRoute.JwtGroupsFilter, src.JWTGroupsFilter, &diagnostics) + ToStringSliceFromSet(ctx, &pbRoute.To, src.To, &diagnostics) + ToStringSliceFromSet(ctx, &pbRoute.PolicyIds, src.Policies, &diagnostics) + pbRoute.TlsClientKeyPairId = src.TLSClientKeyPairID.ValueStringPointer() + pbRoute.TlsCustomCaKeyPairId = src.TLSCustomCAKeyPairID.ValueStringPointer() + pbRoute.Description = src.Description.ValueStringPointer() + pbRoute.LogoUrl = src.LogoURL.ValueStringPointer() + if !src.EnableGoogleCloudServerlessAuthentication.IsNull() { + pbRoute.EnableGoogleCloudServerlessAuthentication = src.EnableGoogleCloudServerlessAuthentication.ValueBool() + } + pbRoute.KubernetesServiceAccountTokenFile = src.KubernetesServiceAccountTokenFile.ValueStringPointer() - diags := src.To.ElementsAs(ctx, &pbRoute.To, false) - diagnostics.Append(diags...) + pbRoute.RewriteResponseHeaders = rewriteHeadersToPB(src.RewriteResponseHeaders) - if !src.Policies.IsNull() { - diags = src.Policies.ElementsAs(ctx, &pbRoute.PolicyIds, false) - diagnostics.Append(diags...) - } return pbRoute, diagnostics } @@ -143,6 +223,17 @@ func ConvertRouteFromPB( dst.IDPClientSecret = types.StringPointerValue(src.IdpClientSecret) dst.ShowErrorDetails = types.BoolValue(src.ShowErrorDetails) JWTGroupsFilterFromPB(&dst.JWTGroupsFilter, src.JwtGroupsFilter) + dst.TLSClientKeyPairID = types.StringPointerValue(src.TlsClientKeyPairId) + dst.TLSCustomCAKeyPairID = types.StringPointerValue(src.TlsCustomCaKeyPairId) + dst.Description = types.StringPointerValue(src.Description) + dst.LogoURL = types.StringPointerValue(src.LogoUrl) + dst.EnableGoogleCloudServerlessAuthentication = types.BoolNull() + if src.EnableGoogleCloudServerlessAuthentication { + dst.EnableGoogleCloudServerlessAuthentication = types.BoolValue(true) + } + dst.KubernetesServiceAccountTokenFile = types.StringPointerValue(src.KubernetesServiceAccountTokenFile) + + dst.RewriteResponseHeaders = rewriteHeadersFromPB(src.RewriteResponseHeaders) return diagnostics } diff --git a/internal/provider/route_test.go b/internal/provider/route_test.go index 9bf5b11..18fcf93 100644 --- a/internal/provider/route_test.go +++ b/internal/provider/route_test.go @@ -34,10 +34,22 @@ func TestConvertRoute(t *testing.T) { NamespaceId: "namespace-1", StatName: "stats-name", PolicyIds: []string{"policy-1", "policy-2"}, + RewriteResponseHeaders: []*pb.RouteRewriteHeader{ + { + Header: "header-1", + Matcher: &pb.RouteRewriteHeader_Prefix{Prefix: "prefix-1"}, + Value: "value-1", + }, + }, SetResponseHeaders: map[string]string{ "X-Response": "value", }, ShowErrorDetails: true, + Description: P("route description"), + LogoUrl: P("https://example.com/logo.png"), + EnableGoogleCloudServerlessAuthentication: true, + TlsCustomCaKeyPairId: P("custom-ca-1"), + KubernetesServiceAccountTokenFile: P("/path/to/token"), } var actual provider.RouteResourceModel @@ -71,6 +83,19 @@ func TestConvertRoute(t *testing.T) { types.StringValue("policy-1"), types.StringValue("policy-2"), }), + RewriteResponseHeaders: types.SetValueMust( + provider.RewriteHeaderObjectType(), + []attr.Value{ + types.ObjectValueMust( + provider.RewriteHeaderAttrTypes(), + map[string]attr.Value{ + "header": types.StringValue("header-1"), + "prefix": types.StringValue("prefix-1"), + "value": types.StringValue("value-1"), + }, + ), + }, + ), SetResponseHeaders: types.MapValueMust( types.StringType, map[string]attr.Value{ @@ -78,6 +103,11 @@ func TestConvertRoute(t *testing.T) { }, ), ShowErrorDetails: types.BoolValue(true), + Description: types.StringValue("route description"), + LogoURL: types.StringValue("https://example.com/logo.png"), + EnableGoogleCloudServerlessAuthentication: types.BoolValue(true), + TLSCustomCAKeyPairID: types.StringValue("custom-ca-1"), + KubernetesServiceAccountTokenFile: types.StringValue("/path/to/token"), } if diff := cmp.Diff(expected, actual); diff != "" { @@ -122,6 +152,11 @@ func TestConvertRoute(t *testing.T) { }, ), ShowErrorDetails: types.BoolValue(true), + Description: types.StringValue("route description"), + LogoURL: types.StringValue("https://example.com/logo.png"), + EnableGoogleCloudServerlessAuthentication: types.BoolValue(true), + TLSCustomCAKeyPairID: types.StringValue("custom-ca-1"), + KubernetesServiceAccountTokenFile: types.StringValue("/path/to/token"), } actual, diag := provider.ConvertRouteToPB(context.Background(), &input) @@ -142,10 +177,212 @@ func TestConvertRoute(t *testing.T) { PolicyIds: []string{"policy-1", "policy-2"}, SetResponseHeaders: map[string]string{"X-Response": "value"}, ShowErrorDetails: true, + Description: P("route description"), + LogoUrl: P("https://example.com/logo.png"), + EnableGoogleCloudServerlessAuthentication: true, + TlsCustomCaKeyPairId: P("custom-ca-1"), + KubernetesServiceAccountTokenFile: P("/path/to/token"), } if diff := cmp.Diff(expected, actual, protocmp.Transform()); diff != "" { t.Errorf("unexpected difference: %s", diff) } }) + + t.Run("pb to model with rewrite headers", func(t *testing.T) { + t.Parallel() + + input := &pb.Route{ + Id: "route-id", + Name: "route-name", + From: "from", + To: []string{"to1", "to2"}, + Prefix: P("/api"), + Path: P("/v1"), + PassIdentityHeaders: P(true), + SetRequestHeaders: map[string]string{ + "X-Custom": "value", + }, + RemoveRequestHeaders: []string{"Remove-Me"}, + NamespaceId: "namespace-1", + StatName: "stats-name", + PolicyIds: []string{"policy-1", "policy-2"}, + RewriteResponseHeaders: []*pb.RouteRewriteHeader{ + { + Header: "header-1", + Matcher: &pb.RouteRewriteHeader_Prefix{Prefix: "prefix-1"}, + Value: "value-1", + }, + { + Header: "header-2", + Value: "value-2", + }, + }, + SetResponseHeaders: map[string]string{ + "X-Response": "value", + }, + ShowErrorDetails: true, + Description: P("route description"), + LogoUrl: P("https://example.com/logo.png"), + EnableGoogleCloudServerlessAuthentication: true, + TlsCustomCaKeyPairId: P("custom-ca-1"), + KubernetesServiceAccountTokenFile: P("/path/to/token"), + } + + var actual provider.RouteResourceModel + diag := provider.ConvertRouteFromPB(&actual, input) + require.False(t, diag.HasError(), diag.Errors()) + + expected := provider.RouteResourceModel{ + ID: types.StringValue("route-id"), + Name: types.StringValue("route-name"), + From: types.StringValue("from"), + Prefix: types.StringValue("/api"), + Path: types.StringValue("/v1"), + PassIdentityHeaders: types.BoolValue(true), + To: types.SetValueMust(types.StringType, []attr.Value{ + types.StringValue("to1"), + types.StringValue("to2"), + }), + SetRequestHeaders: types.MapValueMust( + types.StringType, + map[string]attr.Value{ + "X-Custom": types.StringValue("value"), + }, + ), + RemoveRequestHeaders: types.SetValueMust( + types.StringType, + []attr.Value{types.StringValue("Remove-Me")}, + ), + NamespaceID: types.StringValue("namespace-1"), + StatName: types.StringValue("stats-name"), + Policies: types.SetValueMust(types.StringType, []attr.Value{ + types.StringValue("policy-1"), + types.StringValue("policy-2"), + }), + RewriteResponseHeaders: types.SetValueMust( + provider.RewriteHeaderObjectType(), + []attr.Value{ + types.ObjectValueMust( + provider.RewriteHeaderAttrTypes(), + map[string]attr.Value{ + "header": types.StringValue("header-1"), + "prefix": types.StringValue("prefix-1"), + "value": types.StringValue("value-1"), + }, + ), + types.ObjectValueMust( + provider.RewriteHeaderAttrTypes(), + map[string]attr.Value{ + "header": types.StringValue("header-2"), + "prefix": types.StringNull(), + "value": types.StringValue("value-2"), + }, + ), + }, + ), + SetResponseHeaders: types.MapValueMust( + types.StringType, + map[string]attr.Value{ + "X-Response": types.StringValue("value"), + }, + ), + ShowErrorDetails: types.BoolValue(true), + Description: types.StringValue("route description"), + LogoURL: types.StringValue("https://example.com/logo.png"), + EnableGoogleCloudServerlessAuthentication: types.BoolValue(true), + TLSCustomCAKeyPairID: types.StringValue("custom-ca-1"), + KubernetesServiceAccountTokenFile: types.StringValue("/path/to/token"), + } + + if diff := cmp.Diff(expected.RewriteResponseHeaders, actual.RewriteResponseHeaders); diff != "" { + t.Errorf("unexpected difference in RewriteResponseHeaders: %s", diff) + } + }) + + t.Run("model to pb with rewrite headers", func(t *testing.T) { + t.Parallel() + + input := provider.RouteResourceModel{ + ID: types.StringValue("route-id"), + Name: types.StringValue("route-name"), + From: types.StringValue("from"), + To: types.SetValueMust(types.StringType, []attr.Value{ + types.StringValue("to1"), + types.StringValue("to2"), + }), + Prefix: types.StringValue("/api"), + Path: types.StringValue("/v1"), + PassIdentityHeaders: types.BoolValue(true), + SetRequestHeaders: types.MapValueMust( + types.StringType, + map[string]attr.Value{ + "X-Custom": types.StringValue("value"), + }, + ), + RemoveRequestHeaders: types.SetValueMust( + types.StringType, + []attr.Value{types.StringValue("Remove-Me")}, + ), + NamespaceID: types.StringValue("namespace-1"), + StatName: types.StringValue("stats-name"), + Policies: types.SetValueMust(types.StringType, []attr.Value{ + types.StringValue("policy-1"), + types.StringValue("policy-2"), + }), + RewriteResponseHeaders: types.SetValueMust( + provider.RewriteHeaderObjectType(), + []attr.Value{ + types.ObjectValueMust( + provider.RewriteHeaderAttrTypes(), + map[string]attr.Value{ + "header": types.StringValue("header-1"), + "prefix": types.StringValue("prefix-1"), + "value": types.StringValue("value-1"), + }, + ), + types.ObjectValueMust( + provider.RewriteHeaderAttrTypes(), + map[string]attr.Value{ + "header": types.StringValue("header-2"), + "prefix": types.StringNull(), + "value": types.StringValue("value-2"), + }, + ), + }, + ), + SetResponseHeaders: types.MapValueMust( + types.StringType, + map[string]attr.Value{ + "X-Response": types.StringValue("value"), + }, + ), + ShowErrorDetails: types.BoolValue(true), + Description: types.StringValue("route description"), + LogoURL: types.StringValue("https://example.com/logo.png"), + EnableGoogleCloudServerlessAuthentication: types.BoolValue(true), + TLSCustomCAKeyPairID: types.StringValue("custom-ca-1"), + KubernetesServiceAccountTokenFile: types.StringValue("/path/to/token"), + } + + actual, diag := provider.ConvertRouteToPB(context.Background(), &input) + require.False(t, diag.HasError(), diag.Errors()) + + expectedHeaders := []*pb.RouteRewriteHeader{ + { + Header: "header-1", + Matcher: &pb.RouteRewriteHeader_Prefix{Prefix: "prefix-1"}, + Value: "value-1", + }, + { + Header: "header-2", + Value: "value-2", + // no prefix matcher for null case + }, + } + + if diff := cmp.Diff(expectedHeaders, actual.RewriteResponseHeaders, protocmp.Transform()); diff != "" { + t.Errorf("unexpected difference in RewriteResponseHeaders: %s", diff) + } + }) } From 66f71193a0a46c4baee9d7d73f32efc087ec5fa4 Mon Sep 17 00:00:00 2001 From: Denis Mishin Date: Fri, 31 Jan 2025 16:08:50 -0500 Subject: [PATCH 2/3] rm redirect opts, will be separate pr --- internal/provider/route.go | 14 -------------- internal/provider/route_model.go | 1 - 2 files changed, 15 deletions(-) diff --git a/internal/provider/route.go b/internal/provider/route.go index 1be433a..705fb73 100644 --- a/internal/provider/route.go +++ b/internal/provider/route.go @@ -251,20 +251,6 @@ func (r *RouteResource) Schema(_ context.Context, _ resource.SchemaRequest, resp Description: "Enable Google Cloud serverless authentication.", Optional: true, }, - "redirect": schema.ObjectAttribute{ - Description: "Redirect configuration.", - Optional: true, - AttributeTypes: map[string]attr.Type{ - "host_redirect": types.StringType, - "https_redirect": types.BoolType, - "path_redirect": types.StringType, - "prefix_rewrite": types.StringType, - "response_code": types.Int64Type, - "strip_query": types.BoolType, - "scheme_redirect": types.StringType, - "port_redirect": types.Int64Type, - }, - }, }, } } diff --git a/internal/provider/route_model.go b/internal/provider/route_model.go index f47cfe6..d43f1cf 100644 --- a/internal/provider/route_model.go +++ b/internal/provider/route_model.go @@ -38,7 +38,6 @@ type RouteModel struct { Prefix types.String `tfsdk:"prefix"` PrefixRewrite types.String `tfsdk:"prefix_rewrite"` PreserveHostHeader types.Bool `tfsdk:"preserve_host_header"` - Redirect types.Object `tfsdk:"redirect"` Regex types.String `tfsdk:"regex"` RegexPriorityOrder types.Int64 `tfsdk:"regex_priority_order"` RegexRewritePattern types.String `tfsdk:"regex_rewrite_pattern"` From 82e00efefb1ed70c6ed4fe8bad65c4e20bf4b62e Mon Sep 17 00:00:00 2001 From: Denis Mishin Date: Mon, 3 Feb 2025 17:03:03 -0500 Subject: [PATCH 3/3] update route data source --- example/main.tf | 6 +-- internal/provider/route_data_source.go | 59 ++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/example/main.tf b/example/main.tf index 7dae9aa..c05247b 100644 --- a/example/main.tf +++ b/example/main.tf @@ -245,9 +245,9 @@ data "pomerium_namespace" "existing_namespace" { id = pomerium_namespace.test_namespace.id } -# data "pomerium_route" "existing_route" { -# id = pomerium_route.test_route.id -# } +data "pomerium_route" "existing_route" { + id = pomerium_route.test_route.id +} # Output examples output "namespace_name" { diff --git a/internal/provider/route_data_source.go b/internal/provider/route_data_source.go index 00be99e..fb7d82b 100644 --- a/internal/provider/route_data_source.go +++ b/internal/provider/route_data_source.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" @@ -30,7 +31,7 @@ func getRouteDataSourceAttributes(idRequired bool) map[string]schema.Attribute { Computed: true, Description: "From URL.", }, - "to": schema.ListAttribute{ + "to": schema.SetAttribute{ Computed: true, ElementType: types.StringType, Description: "To URLs.", @@ -39,7 +40,7 @@ func getRouteDataSourceAttributes(idRequired bool) map[string]schema.Attribute { Computed: true, Description: "ID of the namespace the route belongs to.", }, - "policies": schema.ListAttribute{ + "policies": schema.SetAttribute{ Computed: true, ElementType: types.StringType, Description: "List of policy IDs associated with the route.", @@ -131,7 +132,7 @@ func getRouteDataSourceAttributes(idRequired bool) map[string]schema.Attribute { ElementType: types.StringType, Description: "Set request headers.", }, - "remove_request_headers": schema.ListAttribute{ + "remove_request_headers": schema.SetAttribute{ Computed: true, ElementType: types.StringType, Description: "Remove request headers.", @@ -165,6 +166,58 @@ func getRouteDataSourceAttributes(idRequired bool) map[string]schema.Attribute { Computed: true, Description: "Show error details.", }, + "jwt_groups_filter": JWTGroupsFilterSchema, + "jwt_issuer_format": schema.ObjectAttribute{ + Description: "JWT issuer format configuration.", + Computed: true, + AttributeTypes: map[string]attr.Type{ + "format": types.StringType, + }, + }, + "rewrite_response_headers": schema.SetNestedAttribute{ + Description: "Response header rewrite rules.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "header": schema.StringAttribute{ + Required: true, + Description: "Header name to rewrite", + }, + "prefix": schema.StringAttribute{ + Optional: true, + Description: "Prefix matcher for the header", + }, + "value": schema.StringAttribute{ + Required: true, + Description: "New value for the header", + }, + }, + }, + }, + "tls_custom_ca_key_pair_id": schema.StringAttribute{ + Description: "Custom CA key pair ID for TLS verification.", + Computed: true, + }, + "tls_client_key_pair_id": schema.StringAttribute{ + Description: "Client key pair ID for TLS client authentication.", + Computed: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the route.", + Computed: true, + }, + "kubernetes_service_account_token_file": schema.StringAttribute{ + Description: "Path to the Kubernetes service account token file.", + Computed: true, + }, + "logo_url": schema.StringAttribute{ + Description: "URL to the logo image.", + Computed: true, + }, + "enable_google_cloud_serverless_authentication": schema.BoolAttribute{ + Description: "Enable Google Cloud serverless authentication.", + Computed: true, + }, } }