diff --git a/internal/client/errors.go b/internal/client/errors.go index dca7a84..044d2a7 100644 --- a/internal/client/errors.go +++ b/internal/client/errors.go @@ -13,12 +13,21 @@ type APIError struct { Errors map[string][]string } +// maxErrorBodyLen is the maximum number of bytes from the API response body +// to include in error messages, to avoid leaking sensitive data from verbose +// error responses. +const maxErrorBodyLen = 500 + func (e *APIError) Error() string { if len(e.Errors) > 0 { return fmt.Sprintf("VirtFusion API error %d (%s): %v", e.StatusCode, e.Status, e.Errors) } if e.Body != "" { - return fmt.Sprintf("VirtFusion API error %d (%s): %s", e.StatusCode, e.Status, e.Body) + body := e.Body + if len(body) > maxErrorBodyLen { + body = body[:maxErrorBodyLen] + "... (truncated)" + } + return fmt.Sprintf("VirtFusion API error %d (%s): %s", e.StatusCode, e.Status, body) } return fmt.Sprintf("VirtFusion API error %d (%s)", e.StatusCode, e.Status) } diff --git a/internal/provider/data_source_user.go b/internal/provider/data_source_user.go index 0492567..b3735e4 100644 --- a/internal/provider/data_source_user.go +++ b/internal/provider/data_source_user.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "fmt" + "net/url" "terraform-provider-virtfusion/internal/client" @@ -105,7 +106,7 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r return } - rawResp, err := d.client.Get(ctx, fmt.Sprintf("/users/%s/byExtRelation", data.ExtRelationID.ValueString())) + rawResp, err := d.client.Get(ctx, fmt.Sprintf("/users/%s/byExtRelation", url.PathEscape(data.ExtRelationID.ValueString()))) if err != nil { resp.Diagnostics.AddError("Error Reading User", err.Error()) return diff --git a/internal/provider/resource_server_firewall.go b/internal/provider/resource_server_firewall.go index fcaad4a..2a853e8 100644 --- a/internal/provider/resource_server_firewall.go +++ b/internal/provider/resource_server_firewall.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "net/url" "terraform-provider-virtfusion/internal/client" @@ -138,7 +139,7 @@ func (r *ServerFirewallResource) Create(ctx context.Context, req resource.Create iface := data.InterfaceName.ValueString() // Enable the firewall - _, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/firewall/%s/enable", serverID, iface), nil) + _, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/firewall/%s/enable", serverID, url.PathEscape(iface)), nil) if err != nil { resp.Diagnostics.AddError("Error enabling server firewall", err.Error()) return @@ -153,7 +154,7 @@ func (r *ServerFirewallResource) Create(ctx context.Context, req resource.Create if len(rules) > 0 { rulesReq := client.FirewallSetRulesRequest{Rules: rules} - _, err = r.client.Post(ctx, fmt.Sprintf("/servers/%d/firewall/%s/rules", serverID, iface), rulesReq) + _, err = r.client.Post(ctx, fmt.Sprintf("/servers/%d/firewall/%s/rules", serverID, url.PathEscape(iface)), rulesReq) if err != nil { resp.Diagnostics.AddError("Error setting firewall rules", err.Error()) return @@ -175,7 +176,7 @@ func (r *ServerFirewallResource) Read(ctx context.Context, req resource.ReadRequ serverID := data.ServerID.ValueInt64() iface := data.InterfaceName.ValueString() - result, err := r.client.Get(ctx, fmt.Sprintf("/servers/%d/firewall/%s", serverID, iface)) + result, err := r.client.Get(ctx, fmt.Sprintf("/servers/%d/firewall/%s", serverID, url.PathEscape(iface))) if err != nil { var apiErr *client.APIError if errors.As(err, &apiErr) && apiErr.IsNotFound() { @@ -247,7 +248,7 @@ func (r *ServerFirewallResource) Update(ctx context.Context, req resource.Update } rulesReq := client.FirewallSetRulesRequest{Rules: rules} - _, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/firewall/%s/rules", serverID, iface), rulesReq) + _, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/firewall/%s/rules", serverID, url.PathEscape(iface)), rulesReq) if err != nil { resp.Diagnostics.AddError("Error updating firewall rules", err.Error()) return @@ -268,7 +269,7 @@ func (r *ServerFirewallResource) Delete(ctx context.Context, req resource.Delete serverID := data.ServerID.ValueInt64() iface := data.InterfaceName.ValueString() - _, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/firewall/%s/disable", serverID, iface), nil) + _, err := r.client.Post(ctx, fmt.Sprintf("/servers/%d/firewall/%s/disable", serverID, url.PathEscape(iface)), nil) if err != nil { var apiErr *client.APIError if errors.As(err, &apiErr) && apiErr.IsNotFound() { diff --git a/internal/provider/resource_user.go b/internal/provider/resource_user.go index 52f667f..8afd0dc 100644 --- a/internal/provider/resource_user.go +++ b/internal/provider/resource_user.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "net/url" "terraform-provider-virtfusion/internal/client" @@ -137,7 +138,7 @@ func (r *UserResource) Read(ctx context.Context, req resource.ReadRequest, resp return } - result, err := r.client.Get(ctx, fmt.Sprintf("/users/%s/byExtRelation", data.ExtRelationID.ValueString())) + result, err := r.client.Get(ctx, fmt.Sprintf("/users/%s/byExtRelation", url.PathEscape(data.ExtRelationID.ValueString()))) if err != nil { var apiErr *client.APIError if errors.As(err, &apiErr) && apiErr.IsNotFound() { @@ -175,7 +176,7 @@ func (r *UserResource) Update(ctx context.Context, req resource.UpdateRequest, r Email: data.Email.ValueString(), } - result, err := r.client.Put(ctx, fmt.Sprintf("/users/%s/byExtRelation", data.ExtRelationID.ValueString()), body) + result, err := r.client.Put(ctx, fmt.Sprintf("/users/%s/byExtRelation", url.PathEscape(data.ExtRelationID.ValueString())), body) if err != nil { resp.Diagnostics.AddError("Error updating user", err.Error()) return @@ -203,7 +204,7 @@ func (r *UserResource) Delete(ctx context.Context, req resource.DeleteRequest, r return } - _, err := r.client.Delete(ctx, fmt.Sprintf("/users/%s/byExtRelation", data.ExtRelationID.ValueString())) + _, err := r.client.Delete(ctx, fmt.Sprintf("/users/%s/byExtRelation", url.PathEscape(data.ExtRelationID.ValueString()))) if err != nil { var apiErr *client.APIError if errors.As(err, &apiErr) && apiErr.IsNotFound() { diff --git a/internal/provider/resource_user_auth_token.go b/internal/provider/resource_user_auth_token.go index 9cbbfc9..e80eaaf 100644 --- a/internal/provider/resource_user_auth_token.go +++ b/internal/provider/resource_user_auth_token.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "fmt" + "net/url" "time" "terraform-provider-virtfusion/internal/client" @@ -74,6 +75,7 @@ func (r *UserAuthTokenResource) Schema(_ context.Context, _ resource.SchemaReque "url": schema.StringAttribute{ MarkdownDescription: "The authentication URL for the generated token.", Computed: true, + Sensitive: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -114,7 +116,7 @@ func (r *UserAuthTokenResource) Create(ctx context.Context, req resource.CreateR return } - apiPath := fmt.Sprintf("/users/%s/authenticationTokens", data.ExtRelationID.ValueString()) + apiPath := fmt.Sprintf("/users/%s/authenticationTokens", url.PathEscape(data.ExtRelationID.ValueString())) rawResp, err := r.client.Post(ctx, apiPath, nil) if err != nil { resp.Diagnostics.AddError( diff --git a/internal/provider/resource_user_password_reset.go b/internal/provider/resource_user_password_reset.go index 93c3a2e..81b6172 100644 --- a/internal/provider/resource_user_password_reset.go +++ b/internal/provider/resource_user_password_reset.go @@ -6,6 +6,7 @@ package provider import ( "context" "fmt" + "net/url" "time" "terraform-provider-virtfusion/internal/client" @@ -96,7 +97,7 @@ func (r *UserPasswordResetResource) Create(ctx context.Context, req resource.Cre return } - apiPath := fmt.Sprintf("/users/%s/byExtRelation/resetPassword", data.ExtRelationID.ValueString()) + apiPath := fmt.Sprintf("/users/%s/byExtRelation/resetPassword", url.PathEscape(data.ExtRelationID.ValueString())) _, err := r.client.Post(ctx, apiPath, nil) if err != nil { resp.Diagnostics.AddError( diff --git a/internal/provider/resource_user_server_auth_token.go b/internal/provider/resource_user_server_auth_token.go index 98cc149..e77ff37 100644 --- a/internal/provider/resource_user_server_auth_token.go +++ b/internal/provider/resource_user_server_auth_token.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "fmt" + "net/url" "time" "terraform-provider-virtfusion/internal/client" @@ -80,6 +81,7 @@ func (r *UserServerAuthTokenResource) Schema(_ context.Context, _ resource.Schem "url": schema.StringAttribute{ MarkdownDescription: "The authentication URL for the generated server token.", Computed: true, + Sensitive: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -120,7 +122,7 @@ func (r *UserServerAuthTokenResource) Create(ctx context.Context, req resource.C return } - apiPath := fmt.Sprintf("/users/%s/serverAuthenticationTokens/%d", data.ExtRelationID.ValueString(), data.ServerID.ValueInt64()) + apiPath := fmt.Sprintf("/users/%s/serverAuthenticationTokens/%d", url.PathEscape(data.ExtRelationID.ValueString()), data.ServerID.ValueInt64()) rawResp, err := r.client.Post(ctx, apiPath, nil) if err != nil { resp.Diagnostics.AddError(