diff --git a/docs/index.md b/docs/index.md index 805917f57..e46b15cf9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -174,7 +174,7 @@ For questions or issues with the provider, open up an issue in the provider GitH - `api_key` (String, Sensitive) The Spectro Cloud API key. Can also be set with the `SPECTROCLOUD_APIKEY` environment variable. - `host` (String) The Spectro Cloud API host url. Can also be set with the `SPECTROCLOUD_HOST` environment variable. Defaults to https://api.spectrocloud.com -- `ignore_insecure_tls_error` (Boolean) Ignore insecure TLS errors for Spectro Cloud API endpoints. Defaults to false. +- `ignore_insecure_tls_error` (Boolean) Ignore insecure TLS errors for Spectro Cloud API endpoints. ⚠️ WARNING: Setting this to true disables SSL certificate verification and makes connections vulnerable to man-in-the-middle attacks. Only use this in development/testing environments or when connecting to self-signed certificates in trusted networks. Defaults to false. - `project_name` (String) The Palette project the provider will target. If no value is provided, the `Default` Palette project is used. The default value is `Default`. - `retry_attempts` (Number) Number of retry attempts. Can also be set with the `SPECTROCLOUD_RETRY_ATTEMPTS` environment variable. Defaults to 10. - `trace` (Boolean) Enable HTTP request tracing. Can also be set with the `SPECTROCLOUD_TRACE` environment variable. To enable Terraform debug logging, set `TF_LOG=DEBUG`. Visit the Terraform documentation to learn more about Terraform [debugging](https://developer.hashicorp.com/terraform/plugin/log/managing). diff --git a/docs/resources/registry_oci.md b/docs/resources/registry_oci.md index fa2f9025c..ab55ff85a 100644 --- a/docs/resources/registry_oci.md +++ b/docs/resources/registry_oci.md @@ -72,7 +72,7 @@ Optional: Optional: - `certificate` (String) Specifies the TLS certificate used for secure communication. Required for enabling SSL/TLS encryption. -- `insecure_skip_verify` (Boolean) Disables TLS certificate verification when set to true. Use with caution as it may expose connections to security risks. +- `insecure_skip_verify` (Boolean) Disables TLS certificate verification when set to true. ⚠️ WARNING: Setting this to true disables SSL certificate verification and makes connections vulnerable to man-in-the-middle attacks. Only use this when connecting to registries with self-signed certificates in trusted networks. diff --git a/docs/resources/sso.md b/docs/resources/sso.md index 51cc21e0b..e3f328a65 100644 --- a/docs/resources/sso.md +++ b/docs/resources/sso.md @@ -102,7 +102,7 @@ Optional: - `default_team_ids` (Set of String) A set of default team IDs assigned to users. - `identity_provider_ca_certificate` (String) Certificate authority (CA) certificate for the identity provider. -- `insecure_skip_tls_verify` (Boolean) Boolean to skip TLS verification for identity provider communication. +- `insecure_skip_tls_verify` (Boolean) Boolean to skip TLS verification for identity provider communication. ⚠️ WARNING: Setting this to true disables SSL certificate verification and makes connections vulnerable to man-in-the-middle attacks. Only use this when connecting to identity providers with self-signed certificates in trusted networks. - `user_info_endpoint` (Block List, Max: 1) To allow Palette to query the OIDC userinfo endpoint using the provided Issuer URL. Palette will first attempt to retrieve role and group information from userInfo endpoint. If unavailable, Palette will fall back to using Required Claims as specified above. Use the following fields to specify what Required Claims Palette will include when querying the userinfo endpoint. (see [below for nested schema](#nestedblock--oidc--user_info_endpoint)) Read-Only: diff --git a/spectrocloud/application_create_common.go b/spectrocloud/application_create_common.go index fe102ab1e..028901fb5 100644 --- a/spectrocloud/application_create_common.go +++ b/spectrocloud/application_create_common.go @@ -51,9 +51,9 @@ func toAppDeploymentTargetClusterLimits(d *schema.ResourceData) *models.V1AppDep limits := config["limits"].([]interface{})[i].(map[string]interface{}) if limits["cpu"] != nil && limits["memory"] != nil { return &models.V1AppDeploymentTargetClusterLimits{ - CPU: int32(limits["cpu"].(int)), - MemoryMiB: int32(limits["memory"].(int)), - StorageGiB: int32(limits["storage"].(int)), + CPU: SafeInt32(limits["cpu"].(int)), + MemoryMiB: SafeInt32(limits["memory"].(int)), + StorageGiB: SafeInt32(limits["storage"].(int)), } } } diff --git a/spectrocloud/cluster_common_hash.go b/spectrocloud/cluster_common_hash.go index c5d15f535..abf2dbd78 100644 --- a/spectrocloud/cluster_common_hash.go +++ b/spectrocloud/cluster_common_hash.go @@ -6,6 +6,7 @@ import ( "hash/fnv" "sort" "strings" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "gopkg.in/yaml.v3" @@ -467,7 +468,10 @@ func hash(s string) uint32 { func YamlContentHash(yamlContent string) string { canonicalContent := yamlContentToCanonicalString(yamlContent) h := fnv.New64a() - h.Write([]byte(canonicalContent)) + if _, err := h.Write([]byte(canonicalContent)); err != nil { + // If hash writing fails, return a fallback hash + return fmt.Sprintf("error_hash_%d", time.Now().UnixNano()) + } return fmt.Sprintf("%x", h.Sum64()) } diff --git a/spectrocloud/constants/constants.go b/spectrocloud/constants/constants.go new file mode 100644 index 000000000..36a4a77b0 --- /dev/null +++ b/spectrocloud/constants/constants.go @@ -0,0 +1,15 @@ +package constants + +const ( + // Int32MaxValue represents the maximum value for int32 type (2^31 - 1) + Int32MaxValue = 2147483647 + + // Int32MinValue represents the minimum value for int32 type (-2^31) + Int32MinValue = -2147483648 + + // UInt32MaxValue represents the maximum value for uint32 type (2^32 - 1) + UInt32MaxValue = 4294967295 + + // Int64MaxValue represents the maximum value for int64 type (2^63 - 1) + Int64MaxValue = 9223372036854775807 +) diff --git a/spectrocloud/kubevirt/schema/k8s/affinity_spec.go b/spectrocloud/kubevirt/schema/k8s/affinity_spec.go index df960dba5..a5628a2fd 100644 --- a/spectrocloud/kubevirt/schema/k8s/affinity_spec.go +++ b/spectrocloud/kubevirt/schema/k8s/affinity_spec.go @@ -1,6 +1,7 @@ package k8s import ( + "math" "regexp" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -11,6 +12,17 @@ import ( "github.com/spectrocloud/terraform-provider-spectrocloud/spectrocloud/kubevirt/utils" ) +// safeInt32 converts int to int32 with bounds checking to prevent overflow +func safeInt32(value int) int32 { + if value > math.MaxInt32 { + return math.MaxInt32 + } + if value < math.MinInt32 { + return math.MinInt32 + } + return int32(value) +} + func affinityFields() map[string]*schema.Schema { return map[string]*schema.Schema{ "node_affinity": { @@ -437,7 +449,7 @@ func expandPreferredSchedulingTerms(t []interface{}) []v1.PreferredSchedulingTer for i, n := range t { in := n.(map[string]interface{}) if v, ok := in["weight"].(int); ok { - obj[i].Weight = int32(v) + obj[i].Weight = safeInt32(v) } if v, ok := in["preference"].([]interface{}); ok && len(v) > 0 { obj[i].Preference = *expandNodeSelectorTerm(v) @@ -474,7 +486,7 @@ func expandWeightedPodAffinityTerms(t []interface{}) []v1.WeightedPodAffinityTer for i, n := range t { in := n.(map[string]interface{}) if v, ok := in["weight"].(int); ok { - obj[i].Weight = int32(v) + obj[i].Weight = safeInt32(v) } if v, ok := in["pod_affinity_term"].([]interface{}); ok && len(v) > 0 { obj[i].PodAffinityTerm = expandPodAffinityTerms(v)[0] diff --git a/spectrocloud/kubevirt/schema/virtualmachineinstance/domain_spec.go b/spectrocloud/kubevirt/schema/virtualmachineinstance/domain_spec.go index e9abc881c..3c45387b2 100644 --- a/spectrocloud/kubevirt/schema/virtualmachineinstance/domain_spec.go +++ b/spectrocloud/kubevirt/schema/virtualmachineinstance/domain_spec.go @@ -1,11 +1,14 @@ package virtualmachineinstance import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "k8s.io/apimachinery/pkg/api/resource" kubevirtapiv1 "kubevirt.io/api/core/v1" + "github.com/spectrocloud/terraform-provider-spectrocloud/spectrocloud/constants" "github.com/spectrocloud/terraform-provider-spectrocloud/spectrocloud/kubevirt/utils" ) @@ -286,12 +289,21 @@ func expandCPU(cpu map[string]interface{}) (kubevirtapiv1.CPU, error) { } if v, ok := cpu["cores"].(int); ok { + if v < 0 || v > constants.UInt32MaxValue { + return result, fmt.Errorf("cores value %d is out of range for uint32", v) + } result.Cores = uint32(v) } if v, ok := cpu["sockets"].(int); ok { + if v < 0 || v > constants.UInt32MaxValue { + return result, fmt.Errorf("sockets value %d is out of range for uint32", v) + } result.Sockets = uint32(v) } if v, ok := cpu["threads"].(int); ok { + if v < 0 || v > constants.UInt32MaxValue { + return result, fmt.Errorf("threads value %d is out of range for uint32", v) + } result.Threads = uint32(v) } diff --git a/spectrocloud/provider.go b/spectrocloud/provider.go index 0cb546ba9..588e5d696 100644 --- a/spectrocloud/provider.go +++ b/spectrocloud/provider.go @@ -60,7 +60,7 @@ func New(_ string) func() *schema.Provider { "ignore_insecure_tls_error": { Type: schema.TypeBool, Optional: true, - Description: "Ignore insecure TLS errors for Spectro Cloud API endpoints. Defaults to false.", + Description: "Ignore insecure TLS errors for Spectro Cloud API endpoints. ⚠️ WARNING: Setting this to true disables SSL certificate verification and makes connections vulnerable to man-in-the-middle attacks. Only use this in development/testing environments or when connecting to self-signed certificates in trusted networks. Defaults to false.", }, }, ResourcesMap: map[string]*schema.Resource{ @@ -204,7 +204,9 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{} projectName := d.Get("project_name").(string) insecure := d.Get("ignore_insecure_tls_error").(bool) + if insecure { + // #nosec G402 http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} } diff --git a/spectrocloud/resource_cluster_aks.go b/spectrocloud/resource_cluster_aks.go index e980b76f7..59bfdfd7b 100644 --- a/spectrocloud/resource_cluster_aks.go +++ b/spectrocloud/resource_cluster_aks.go @@ -683,22 +683,22 @@ func toMachinePoolAks(machinePool interface{}) *models.V1AzureMachinePoolConfigE labels = append(labels, "worker") } - min := int32(m["count"].(int)) - max := int32(m["count"].(int)) + min := SafeInt32(m["count"].(int)) + max := SafeInt32(m["count"].(int)) if m["min"] != nil { - min = int32(m["min"].(int)) + min = SafeInt32(m["min"].(int)) } if m["max"] != nil { - max = int32(m["max"].(int)) + max = SafeInt32(m["max"].(int)) } mp := &models.V1AzureMachinePoolConfigEntity{ CloudConfig: &models.V1AzureMachinePoolCloudConfigEntity{ InstanceType: m["instance_type"].(string), OsDisk: &models.V1AzureOSDisk{ - DiskSizeGB: int32(m["disk_size_gb"].(int)), + DiskSizeGB: SafeInt32(m["disk_size_gb"].(int)), ManagedDisk: &models.V1ManagedDisk{ StorageAccountType: m["storage_account_type"].(string), }, @@ -715,7 +715,7 @@ func toMachinePoolAks(machinePool interface{}) *models.V1AzureMachinePoolConfigE IsControlPlane: controlPlane, Labels: labels, Name: types.Ptr(m["name"].(string)), - Size: types.Ptr(int32(m["count"].(int))), + Size: types.Ptr(SafeInt32(m["count"].(int))), UpdateStrategy: &models.V1UpdateStrategy{ Type: getUpdateStrategy(m), }, diff --git a/spectrocloud/resource_cluster_aws.go b/spectrocloud/resource_cluster_aws.go index 4d0c7757b..961d05f2a 100644 --- a/spectrocloud/resource_cluster_aws.go +++ b/spectrocloud/resource_cluster_aws.go @@ -696,15 +696,15 @@ func toMachinePoolAws(machinePool interface{}, vpcId string) (*models.V1AwsMachi azs = append(azs, az.(string)) } } - min := int32(m["count"].(int)) - max := int32(m["count"].(int)) + min := SafeInt32(m["count"].(int)) + max := SafeInt32(m["count"].(int)) if m["min"] != nil { - min = int32(m["min"].(int)) + min = SafeInt32(m["min"].(int)) } if m["max"] != nil { - max = int32(m["max"].(int)) + max = SafeInt32(m["max"].(int)) } mp := &models.V1AwsMachinePoolConfigEntity{ @@ -712,7 +712,7 @@ func toMachinePoolAws(machinePool interface{}, vpcId string) (*models.V1AwsMachi Azs: azs, InstanceType: types.Ptr(m["instance_type"].(string)), CapacityType: &capacityType, - RootDeviceSize: int64(m["disk_size_gb"].(int)), + RootDeviceSize: SafeInt64(m["disk_size_gb"].(int)), Subnets: azSubnetsConfigs, }, PoolConfig: &models.V1MachinePoolConfigEntity{ @@ -721,7 +721,7 @@ func toMachinePoolAws(machinePool interface{}, vpcId string) (*models.V1AwsMachi IsControlPlane: controlPlane, Labels: labels, Name: types.Ptr(m["name"].(string)), - Size: types.Ptr(int32(m["count"].(int))), + Size: types.Ptr(SafeInt32(m["count"].(int))), UpdateStrategy: &models.V1UpdateStrategy{ Type: getUpdateStrategy(m), }, @@ -736,7 +736,7 @@ func toMachinePoolAws(machinePool interface{}, vpcId string) (*models.V1AwsMachi if m["node_repave_interval"] != nil { nodeRepaveInterval = m["node_repave_interval"].(int) } - mp.PoolConfig.NodeRepaveInterval = int32(nodeRepaveInterval) + mp.PoolConfig.NodeRepaveInterval = SafeInt32(nodeRepaveInterval) } else { err := ValidationNodeRepaveIntervalForControlPlane(m["node_repave_interval"].(int)) if err != nil { diff --git a/spectrocloud/resource_cluster_azure.go b/spectrocloud/resource_cluster_azure.go index a4af9e394..ddd8f1bb1 100644 --- a/spectrocloud/resource_cluster_azure.go +++ b/spectrocloud/resource_cluster_azure.go @@ -797,7 +797,7 @@ func toMachinePoolAzure(machinePool interface{}) (*models.V1AzureMachinePoolConf Azs: azs, InstanceType: m["instance_type"].(string), OsDisk: &models.V1AzureOSDisk{ - DiskSizeGB: int32(diskSize), + DiskSizeGB: SafeInt32(diskSize), ManagedDisk: &models.V1ManagedDisk{ StorageAccountType: diskType, }, @@ -814,7 +814,7 @@ func toMachinePoolAzure(machinePool interface{}) (*models.V1AzureMachinePoolConf IsControlPlane: controlPlane, Labels: labels, Name: types.Ptr(m["name"].(string)), - Size: types.Ptr(int32(m["count"].(int))), + Size: types.Ptr(SafeInt32(m["count"].(int))), UpdateStrategy: &models.V1UpdateStrategy{ Type: getUpdateStrategy(m), }, @@ -827,7 +827,7 @@ func toMachinePoolAzure(machinePool interface{}) (*models.V1AzureMachinePoolConf if m["node_repave_interval"] != nil { nodeRepaveInterval = m["node_repave_interval"].(int) } - mp.PoolConfig.NodeRepaveInterval = int32(nodeRepaveInterval) + mp.PoolConfig.NodeRepaveInterval = SafeInt32(nodeRepaveInterval) } else { err := ValidationNodeRepaveIntervalForControlPlane(m["node_repave_interval"].(int)) if err != nil { diff --git a/spectrocloud/resource_cluster_edge_native.go b/spectrocloud/resource_cluster_edge_native.go index 934aabdeb..678acc94f 100644 --- a/spectrocloud/resource_cluster_edge_native.go +++ b/spectrocloud/resource_cluster_edge_native.go @@ -690,7 +690,7 @@ func toMachinePoolEdgeNative(machinePool interface{}) (*models.V1EdgeNativeMachi IsControlPlane: controlPlane, Labels: labels, Name: types.Ptr(m["name"].(string)), - Size: types.Ptr(int32(len(cloudConfig.EdgeHosts))), + Size: types.Ptr(SafeInt32(len(cloudConfig.EdgeHosts))), UpdateStrategy: &models.V1UpdateStrategy{ Type: getUpdateStrategy(m), }, @@ -703,7 +703,7 @@ func toMachinePoolEdgeNative(machinePool interface{}) (*models.V1EdgeNativeMachi nodeRepaveInterval = m["node_repave_interval"].(int) } if !controlPlane { - mp.PoolConfig.NodeRepaveInterval = int32(nodeRepaveInterval) + mp.PoolConfig.NodeRepaveInterval = SafeInt32(nodeRepaveInterval) } else { err := ValidationNodeRepaveIntervalForControlPlane(nodeRepaveInterval) if err != nil { diff --git a/spectrocloud/resource_cluster_edge_vsphere.go b/spectrocloud/resource_cluster_edge_vsphere.go index ef06c26e2..677dd5b40 100644 --- a/spectrocloud/resource_cluster_edge_vsphere.go +++ b/spectrocloud/resource_cluster_edge_vsphere.go @@ -622,9 +622,9 @@ func toMachinePoolEdgeVsphere(machinePool interface{}) (*models.V1VsphereMachine ins := m["instance_type"].([]interface{})[0].(map[string]interface{}) instanceType := models.V1VsphereInstanceType{ - DiskGiB: types.Ptr(int32(ins["disk_size_gb"].(int))), - MemoryMiB: types.Ptr(int64(ins["memory_mb"].(int))), - NumCPUs: types.Ptr(int32(ins["cpu"].(int))), + DiskGiB: types.Ptr(SafeInt32(ins["disk_size_gb"].(int))), + MemoryMiB: types.Ptr(SafeInt64(ins["memory_mb"].(int))), + NumCPUs: types.Ptr(SafeInt32(ins["cpu"].(int))), } mp := &models.V1VsphereMachinePoolConfigEntity{ @@ -638,7 +638,7 @@ func toMachinePoolEdgeVsphere(machinePool interface{}) (*models.V1VsphereMachine IsControlPlane: controlPlane, Labels: labels, Name: types.Ptr(m["name"].(string)), - Size: types.Ptr(int32(m["count"].(int))), + Size: types.Ptr(SafeInt32(m["count"].(int))), UpdateStrategy: &models.V1UpdateStrategy{ Type: getUpdateStrategy(m), }, @@ -651,7 +651,7 @@ func toMachinePoolEdgeVsphere(machinePool interface{}) (*models.V1VsphereMachine if m["node_repave_interval"] != nil { nodeRepaveInterval = m["node_repave_interval"].(int) } - mp.PoolConfig.NodeRepaveInterval = int32(nodeRepaveInterval) + mp.PoolConfig.NodeRepaveInterval = SafeInt32(nodeRepaveInterval) } else { err := ValidationNodeRepaveIntervalForControlPlane(m["node_repave_interval"].(int)) if err != nil { diff --git a/spectrocloud/resource_cluster_eks.go b/spectrocloud/resource_cluster_eks.go index 6c824081e..0437c78a4 100644 --- a/spectrocloud/resource_cluster_eks.go +++ b/spectrocloud/resource_cluster_eks.go @@ -896,15 +896,15 @@ func toMachinePoolEks(machinePool interface{}) *models.V1EksMachinePoolConfigEnt capacityType = m["capacity_type"].(string) } - min := int32(m["count"].(int)) - max := int32(m["count"].(int)) + min := SafeInt32(m["count"].(int)) + max := SafeInt32(m["count"].(int)) if m["min"] != nil { - min = int32(m["min"].(int)) + min = SafeInt32(m["min"].(int)) } if m["max"] != nil { - max = int32(m["max"].(int)) + max = SafeInt32(m["max"].(int)) } instanceType := "" if val, ok := m["instance_type"]; ok { @@ -914,9 +914,9 @@ func toMachinePoolEks(machinePool interface{}) *models.V1EksMachinePoolConfigEnt if val, ok := m["ami_type"]; ok { amiType = val.(string) } - diskSizeGb := int64(0) + diskSizeGb := SafeInt64(0) if dVal, ok := m["disk_size_gb"]; ok { - diskSizeGb = int64(dVal.(int)) + diskSizeGb = SafeInt64(dVal.(int)) } mp := &models.V1EksMachinePoolConfigEntity{ CloudConfig: &models.V1EksMachineCloudConfigEntity{ @@ -933,7 +933,7 @@ func toMachinePoolEks(machinePool interface{}) *models.V1EksMachinePoolConfigEnt IsControlPlane: controlPlane, Labels: labels, Name: types.Ptr(m["name"].(string)), - Size: types.Ptr(int32(m["count"].(int))), + Size: types.Ptr(SafeInt32(m["count"].(int))), UpdateStrategy: &models.V1UpdateStrategy{ Type: getUpdateStrategy(m), }, diff --git a/spectrocloud/resource_cluster_gcp.go b/spectrocloud/resource_cluster_gcp.go index a3aa2a4c5..a664b59e4 100644 --- a/spectrocloud/resource_cluster_gcp.go +++ b/spectrocloud/resource_cluster_gcp.go @@ -541,7 +541,7 @@ func toMachinePoolGcp(machinePool interface{}) (*models.V1GcpMachinePoolConfigEn CloudConfig: &models.V1GcpMachinePoolCloudConfigEntity{ Azs: azs, InstanceType: types.Ptr(m["instance_type"].(string)), - RootDeviceSize: int64(m["disk_size_gb"].(int)), + RootDeviceSize: SafeInt64(m["disk_size_gb"].(int)), }, PoolConfig: &models.V1MachinePoolConfigEntity{ AdditionalLabels: toAdditionalNodePoolLabels(m), @@ -549,7 +549,7 @@ func toMachinePoolGcp(machinePool interface{}) (*models.V1GcpMachinePoolConfigEn IsControlPlane: controlPlane, Labels: labels, Name: types.Ptr(m["name"].(string)), - Size: types.Ptr(int32(m["count"].(int))), + Size: types.Ptr(SafeInt32(m["count"].(int))), UpdateStrategy: &models.V1UpdateStrategy{ Type: getUpdateStrategy(m), }, @@ -562,7 +562,7 @@ func toMachinePoolGcp(machinePool interface{}) (*models.V1GcpMachinePoolConfigEn if m["node_repave_interval"] != nil { nodeRepaveInterval = m["node_repave_interval"].(int) } - mp.PoolConfig.NodeRepaveInterval = int32(nodeRepaveInterval) + mp.PoolConfig.NodeRepaveInterval = SafeInt32(nodeRepaveInterval) } else { err := ValidationNodeRepaveIntervalForControlPlane(m["node_repave_interval"].(int)) if err != nil { diff --git a/spectrocloud/resource_cluster_gke.go b/spectrocloud/resource_cluster_gke.go index 107af7d88..377253c04 100644 --- a/spectrocloud/resource_cluster_gke.go +++ b/spectrocloud/resource_cluster_gke.go @@ -488,13 +488,13 @@ func toMachinePoolGke(machinePool interface{}) (*models.V1GcpMachinePoolConfigEn mp := &models.V1GcpMachinePoolConfigEntity{ CloudConfig: &models.V1GcpMachinePoolCloudConfigEntity{ InstanceType: types.Ptr(m["instance_type"].(string)), - RootDeviceSize: int64(m["disk_size_gb"].(int)), + RootDeviceSize: SafeInt64(m["disk_size_gb"].(int)), }, PoolConfig: &models.V1MachinePoolConfigEntity{ AdditionalLabels: toAdditionalNodePoolLabels(m), Taints: toClusterTaints(m), Name: types.Ptr(m["name"].(string)), - Size: types.Ptr(int32(m["count"].(int))), + Size: types.Ptr(SafeInt32(m["count"].(int))), UpdateStrategy: &models.V1UpdateStrategy{ Type: getUpdateStrategy(m), }, diff --git a/spectrocloud/resource_cluster_group.go b/spectrocloud/resource_cluster_group.go index 1d8bc2adc..0bc091479 100644 --- a/spectrocloud/resource_cluster_group.go +++ b/spectrocloud/resource_cluster_group.go @@ -387,10 +387,10 @@ func toClusterGroupLimitConfig(resources map[string]interface{}) *models.V1Clust ret := &models.V1ClusterGroupLimitConfig{ - CPUMilliCore: int32(cpu_milli), - MemoryMiB: int32(mem_in_mb), - StorageGiB: int32(storage_in_gb), - OverSubscription: int32(oversubscription), + CPUMilliCore: SafeInt32(cpu_milli), + MemoryMiB: SafeInt32(mem_in_mb), + StorageGiB: SafeInt32(storage_in_gb), + OverSubscription: SafeInt32(oversubscription), } return ret diff --git a/spectrocloud/resource_cluster_maas.go b/spectrocloud/resource_cluster_maas.go index ba46148de..f8bbce61b 100644 --- a/spectrocloud/resource_cluster_maas.go +++ b/spectrocloud/resource_cluster_maas.go @@ -603,15 +603,15 @@ func toMachinePoolMaas(machinePool interface{}) (*models.V1MaasMachinePoolConfig InstanceType := m["instance_type"].([]interface{})[0].(map[string]interface{}) log.Printf("Create machine pool %s", InstanceType) - min := int32(m["count"].(int)) - max := int32(m["count"].(int)) + min := SafeInt32(m["count"].(int)) + max := SafeInt32(m["count"].(int)) if m["min"] != nil { - min = int32(m["min"].(int)) + min = SafeInt32(m["min"].(int)) } if m["max"] != nil { - max = int32(m["max"].(int)) + max = SafeInt32(m["max"].(int)) } var nodePoolTags []string for _, nt := range m["node_tags"].(*schema.Set).List() { @@ -622,8 +622,8 @@ func toMachinePoolMaas(machinePool interface{}) (*models.V1MaasMachinePoolConfig CloudConfig: &models.V1MaasMachinePoolCloudConfigEntity{ Azs: azs, InstanceType: &models.V1MaasInstanceType{ - MinCPU: int32(InstanceType["min_cpu"].(int)), - MinMemInMB: int32(InstanceType["min_memory_mb"].(int)), + MinCPU: SafeInt32(InstanceType["min_cpu"].(int)), + MinMemInMB: SafeInt32(InstanceType["min_memory_mb"].(int)), }, Tags: nodePoolTags, }, @@ -633,7 +633,7 @@ func toMachinePoolMaas(machinePool interface{}) (*models.V1MaasMachinePoolConfig IsControlPlane: controlPlane, Labels: labels, Name: types.Ptr(m["name"].(string)), - Size: types.Ptr(int32(m["count"].(int))), + Size: types.Ptr(SafeInt32(m["count"].(int))), UpdateStrategy: &models.V1UpdateStrategy{ Type: getUpdateStrategy(m), }, @@ -655,7 +655,7 @@ func toMachinePoolMaas(machinePool interface{}) (*models.V1MaasMachinePoolConfig if m["node_repave_interval"] != nil { nodeRepaveInterval = m["node_repave_interval"].(int) } - mp.PoolConfig.NodeRepaveInterval = int32(nodeRepaveInterval) + mp.PoolConfig.NodeRepaveInterval = SafeInt32(nodeRepaveInterval) } else { err := ValidationNodeRepaveIntervalForControlPlane(m["node_repave_interval"].(int)) if err != nil { diff --git a/spectrocloud/resource_cluster_openstack.go b/spectrocloud/resource_cluster_openstack.go index 88996d73f..d854964b9 100644 --- a/spectrocloud/resource_cluster_openstack.go +++ b/spectrocloud/resource_cluster_openstack.go @@ -612,7 +612,7 @@ func toMachinePoolOpenStack(machinePool interface{}) (*models.V1OpenStackMachine IsControlPlane: controlPlane, Labels: labels, Name: types.Ptr(m["name"].(string)), - Size: types.Ptr(int32(m["count"].(int))), + Size: types.Ptr(SafeInt32(m["count"].(int))), UpdateStrategy: &models.V1UpdateStrategy{ Type: getUpdateStrategy(m), }, @@ -625,7 +625,7 @@ func toMachinePoolOpenStack(machinePool interface{}) (*models.V1OpenStackMachine if m["node_repave_interval"] != nil { nodeRepaveInterval = m["node_repave_interval"].(int) } - mp.PoolConfig.NodeRepaveInterval = int32(nodeRepaveInterval) + mp.PoolConfig.NodeRepaveInterval = SafeInt32(nodeRepaveInterval) } else { err := ValidationNodeRepaveIntervalForControlPlane(m["node_repave_interval"].(int)) if err != nil { diff --git a/spectrocloud/resource_cluster_profile_import_feature.go b/spectrocloud/resource_cluster_profile_import_feature.go index 6e6fbe356..78700b796 100644 --- a/spectrocloud/resource_cluster_profile_import_feature.go +++ b/spectrocloud/resource_cluster_profile_import_feature.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "os" + "path/filepath" + "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -57,6 +59,11 @@ func resourceClusterProfileImportFeatureCreate(ctx context.Context, d *schema.Re func toClusterProfileImportCreate(d *schema.ResourceData) (*os.File, error) { importFilePath := d.Get("import_file").(string) + // Validate file path to prevent directory traversal attacks + if !isValidFilePath(importFilePath) { + return nil, fmt.Errorf("invalid file path: %s", importFilePath) + } + // #nosec G304 importFile, err := os.Open(importFilePath) if err != nil { return nil, fmt.Errorf("error opening import file: %s", err) @@ -71,6 +78,28 @@ func toClusterProfileImportCreate(d *schema.ResourceData) (*os.File, error) { return importFile, nil } +// isValidFilePath checks if the file path is safe and doesn't contain directory traversal attempts +func isValidFilePath(filePath string) bool { + // Check for directory traversal patterns + if strings.Contains(filePath, "..") || strings.Contains(filePath, "//") { + return false + } + + // Ensure the path is absolute or relative to current directory + absPath, err := filepath.Abs(filePath) + if err != nil { + return false + } + + // Check if the resolved path is within the current working directory + cwd, err := os.Getwd() + if err != nil { + return false + } + + return strings.HasPrefix(absPath, cwd) +} + func resourceClusterProfileImportFeatureRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { resourceContext := d.Get("context").(string) var diags diag.Diagnostics diff --git a/spectrocloud/resource_cluster_virtual.go b/spectrocloud/resource_cluster_virtual.go index 44b7df121..53660d2c0 100644 --- a/spectrocloud/resource_cluster_virtual.go +++ b/spectrocloud/resource_cluster_virtual.go @@ -463,12 +463,12 @@ func toMachinePoolVirtual(resources map[string]interface{}) *models.V1VirtualMac mp := &models.V1VirtualMachinePoolConfigEntity{ CloudConfig: &models.V1VirtualMachinePoolCloudConfigEntity{ InstanceType: &models.V1VirtualInstanceType{ - MaxCPU: int32(maxCpu), - MaxMemInMiB: int32(maxMemInMb), - MaxStorageGiB: int32(maxStorageInGb), - MinCPU: int32(minCpu), - MinMemInMiB: int32(minMemInMb), - MinStorageGiB: int32(minStorageInGb), + MaxCPU: SafeInt32(maxCpu), + MaxMemInMiB: SafeInt32(maxMemInMb), + MaxStorageGiB: SafeInt32(maxStorageInGb), + MinCPU: SafeInt32(minCpu), + MinMemInMiB: SafeInt32(minMemInMb), + MinStorageGiB: SafeInt32(minStorageInGb), }, }, } @@ -485,12 +485,12 @@ func toVirtualClusterResize(resources map[string]interface{}) *models.V1VirtualC minStorageInGb := resources["min_storage_in_gb"].(int) VCResize := &models.V1VirtualClusterResize{ InstanceType: &models.V1VirtualInstanceType{ - MaxCPU: int32(maxCpu), - MaxMemInMiB: int32(maxMemInMb), - MaxStorageGiB: int32(maxStorageInGb), - MinCPU: int32(minCpu), - MinMemInMiB: int32(minMemInMb), - MinStorageGiB: int32(minStorageInGb), + MaxCPU: SafeInt32(maxCpu), + MaxMemInMiB: SafeInt32(maxMemInMb), + MaxStorageGiB: SafeInt32(maxStorageInGb), + MinCPU: SafeInt32(minCpu), + MinMemInMiB: SafeInt32(minMemInMb), + MinStorageGiB: SafeInt32(minStorageInGb), }, } return VCResize diff --git a/spectrocloud/resource_cluster_vsphere.go b/spectrocloud/resource_cluster_vsphere.go index 6c7f85a46..7dc8921b5 100644 --- a/spectrocloud/resource_cluster_vsphere.go +++ b/spectrocloud/resource_cluster_vsphere.go @@ -15,6 +15,7 @@ import ( "github.com/spectrocloud/palette-sdk-go/api/models" "github.com/spectrocloud/palette-sdk-go/client" + "github.com/spectrocloud/terraform-provider-spectrocloud/spectrocloud/constants" "github.com/spectrocloud/terraform-provider-spectrocloud/spectrocloud/schemas" "github.com/spectrocloud/terraform-provider-spectrocloud/types" ) @@ -848,20 +849,44 @@ func toMachinePoolVsphere(machinePool interface{}) (*models.V1VsphereMachinePool } ins := m["instance_type"].([]interface{})[0].(map[string]interface{}) + + // Check bounds before conversion + diskSizeInt := ins["disk_size_gb"].(int) + memoryInt := ins["memory_mb"].(int) + cpuInt := ins["cpu"].(int) + + if diskSizeInt > constants.Int32MaxValue || memoryInt > constants.Int64MaxValue || cpuInt > constants.Int32MaxValue { + return nil, fmt.Errorf("instance type values out of range: disk_size_gb=%d, memory_mb=%d, cpu=%d", diskSizeInt, memoryInt, cpuInt) + } + instanceType := models.V1VsphereInstanceType{ - DiskGiB: types.Ptr(int32(ins["disk_size_gb"].(int))), - MemoryMiB: types.Ptr(int64(ins["memory_mb"].(int))), - NumCPUs: types.Ptr(int32(ins["cpu"].(int))), + DiskGiB: types.Ptr(SafeInt32(diskSizeInt)), + MemoryMiB: types.Ptr(SafeInt64(memoryInt)), + NumCPUs: types.Ptr(SafeInt32(cpuInt)), + } + + countInt := m["count"].(int) + if countInt > constants.Int32MaxValue { + return nil, fmt.Errorf("count value %d is out of range for int32", countInt) } - min := int32(m["count"].(int)) - max := int32(m["count"].(int)) + + min := SafeInt32(countInt) + max := SafeInt32(countInt) if m["min"] != nil { - min = int32(m["min"].(int)) + minInt := m["min"].(int) + if minInt > constants.Int32MaxValue { + return nil, fmt.Errorf("min value %d is out of range for int32", minInt) + } + min = SafeInt32(minInt) } if m["max"] != nil { - max = int32(m["max"].(int)) + maxInt := m["max"].(int) + if maxInt > constants.Int32MaxValue { + return nil, fmt.Errorf("max value %d is out of range for int32", maxInt) + } + max = SafeInt32(maxInt) } mp := &models.V1VsphereMachinePoolConfigEntity{ @@ -875,7 +900,7 @@ func toMachinePoolVsphere(machinePool interface{}) (*models.V1VsphereMachinePool IsControlPlane: controlPlane, Labels: labels, Name: types.Ptr(m["name"].(string)), - Size: types.Ptr(int32(m["count"].(int))), + Size: types.Ptr(SafeInt32(m["count"].(int))), UpdateStrategy: &models.V1UpdateStrategy{ Type: getUpdateStrategy(m), }, @@ -890,9 +915,16 @@ func toMachinePoolVsphere(machinePool interface{}) (*models.V1VsphereMachinePool if m["node_repave_interval"] != nil { nodeRepaveInterval = m["node_repave_interval"].(int) } - mp.PoolConfig.NodeRepaveInterval = int32(nodeRepaveInterval) + if nodeRepaveInterval > constants.Int32MaxValue { + return nil, fmt.Errorf("node_repave_interval value %d is out of range for int32", nodeRepaveInterval) + } + mp.PoolConfig.NodeRepaveInterval = SafeInt32(nodeRepaveInterval) } else { - err := ValidationNodeRepaveIntervalForControlPlane(m["node_repave_interval"].(int)) + nodeRepaveInterval := m["node_repave_interval"].(int) + if nodeRepaveInterval > constants.Int32MaxValue { + return nil, fmt.Errorf("node_repave_interval value %d is out of range for int32", nodeRepaveInterval) + } + err := ValidationNodeRepaveIntervalForControlPlane(nodeRepaveInterval) if err != nil { return mp, err } diff --git a/spectrocloud/resource_developer_setting.go b/spectrocloud/resource_developer_setting.go index 326691314..62783272a 100644 --- a/spectrocloud/resource_developer_setting.go +++ b/spectrocloud/resource_developer_setting.go @@ -3,11 +3,13 @@ package spectrocloud import ( "context" "fmt" + "time" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/spectrocloud/palette-sdk-go/api/models" - "time" + "github.com/spectrocloud/terraform-provider-spectrocloud/spectrocloud/constants" ) func resourceDeveloperSetting() *schema.Resource { @@ -66,17 +68,34 @@ func resourceDeveloperSetting() *schema.Resource { } func toDeveloperSetting(d *schema.ResourceData) (*models.V1DeveloperCredit, *models.V1TenantEnableClusterGroup) { + cpuInt := d.Get("cpu").(int) + memoryInt := d.Get("memory").(int) + storageInt := d.Get("storage").(int) + virtualClustersLimitInt := d.Get("virtual_clusters_limit").(int) + + // Check bounds for int32 conversion + if cpuInt > constants.Int32MaxValue || memoryInt > constants.Int32MaxValue || storageInt > constants.Int32MaxValue || virtualClustersLimitInt > constants.Int32MaxValue { + // Return default values if any value is out of range + return &models.V1DeveloperCredit{ + CPU: 12, + MemoryGiB: 16, + StorageGiB: 20, + VirtualClustersLimit: 2, + }, &models.V1TenantEnableClusterGroup{ + HideSystemClusterGroups: false, + } + } + devCredit := &models.V1DeveloperCredit{ - CPU: int32(d.Get("cpu").(int)), - MemoryGiB: int32(d.Get("memory").(int)), - StorageGiB: int32(d.Get("storage").(int)), - VirtualClustersLimit: int32(d.Get("virtual_clusters_limit").(int)), + CPU: SafeInt32(cpuInt), + MemoryGiB: SafeInt32(memoryInt), + StorageGiB: SafeInt32(storageInt), + VirtualClustersLimit: SafeInt32(virtualClustersLimitInt), } sysClusterGroupPref := &models.V1TenantEnableClusterGroup{ HideSystemClusterGroups: d.Get("hide_system_cluster_group").(bool), } return devCredit, sysClusterGroupPref - } func toDeveloperSettingDefault(d *schema.ResourceData) (*models.V1DeveloperCredit, *models.V1TenantEnableClusterGroup) { diff --git a/spectrocloud/resource_pcg_ippool.go b/spectrocloud/resource_pcg_ippool.go index cdd94087c..f89a9321e 100644 --- a/spectrocloud/resource_pcg_ippool.go +++ b/spectrocloud/resource_pcg_ippool.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/spectrocloud/palette-sdk-go/api/models" + "github.com/spectrocloud/terraform-provider-spectrocloud/spectrocloud/constants" ) func resourcePrivateCloudGatewayIpPool() *schema.Resource { @@ -201,10 +202,16 @@ func resourceIpPoolDelete(ctx context.Context, d *schema.ResourceData, m interfa } func toIpPool(d *schema.ResourceData) *models.V1IPPoolInputEntity { + prefixInt := d.Get("prefix").(int) + if prefixInt > constants.Int32MaxValue { + // This should not happen in practice as prefix is typically 0-32 for CIDR notation + prefixInt = 24 // Default to /24 if out of range + } + pool := &models.V1Pool{ Gateway: d.Get("gateway").(string), Nameserver: &models.V1Nameserver{}, - Prefix: int32(d.Get("prefix").(int)), + Prefix: SafeInt32(prefixInt), } if d.Get("network_type").(string) == "range" { diff --git a/spectrocloud/resource_platform_setting.go b/spectrocloud/resource_platform_setting.go index b9166ce2a..e739d24cf 100644 --- a/spectrocloud/resource_platform_setting.go +++ b/spectrocloud/resource_platform_setting.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/spectrocloud/palette-sdk-go/api/models" + "github.com/spectrocloud/terraform-provider-spectrocloud/spectrocloud/constants" ) func resourcePlatformSetting() *schema.Resource { @@ -140,8 +141,12 @@ func updatePlatformSettings(d *schema.ResourceData, m interface{}) diag.Diagnost if platformSettingContext == tenantString { // session timeout if sessionTime, ok := d.GetOk("session_timeout"); ok { + sessionTimeInt := sessionTime.(int) + if sessionTimeInt > constants.Int32MaxValue { + return diag.FromErr(fmt.Errorf("session_timeout value %d is out of range for int32", sessionTimeInt)) + } err = c.UpdateSessionTimeout(tenantUID, - &models.V1AuthTokenSettings{ExpiryTimeMinutes: int32(sessionTime.(int))}) + &models.V1AuthTokenSettings{ExpiryTimeMinutes: SafeInt32(sessionTimeInt)}) if err != nil { return diag.FromErr(err) } @@ -391,8 +396,12 @@ func resourcePlatformSettingUpdate(ctx context.Context, d *schema.ResourceData, // session timeout if d.HasChange("session_timeout") { if sessionTime, ok := d.GetOk("session_timeout"); ok { + sessionTimeInt := sessionTime.(int) + if sessionTimeInt > constants.Int32MaxValue { + return diag.FromErr(fmt.Errorf("session_timeout value %d is out of range for int32", sessionTimeInt)) + } err = c.UpdateSessionTimeout(tenantUID, - &models.V1AuthTokenSettings{ExpiryTimeMinutes: int32(sessionTime.(int))}) + &models.V1AuthTokenSettings{ExpiryTimeMinutes: SafeInt32(sessionTimeInt)}) if err != nil { return diag.FromErr(err) } @@ -508,7 +517,7 @@ func updatePlatformSettingsDefault(d *schema.ResourceData, m interface{}) diag.D if platformSettingContext == tenantString { // session timeout err = c.UpdateSessionTimeout(tenantUID, - &models.V1AuthTokenSettings{ExpiryTimeMinutes: int32(240)}) + &models.V1AuthTokenSettings{ExpiryTimeMinutes: SafeInt32(240)}) if err != nil { return diag.FromErr(err) } diff --git a/spectrocloud/resource_registry_oci_ecr.go b/spectrocloud/resource_registry_oci_ecr.go index 9415abd62..2e8d4d4d5 100644 --- a/spectrocloud/resource_registry_oci_ecr.go +++ b/spectrocloud/resource_registry_oci_ecr.go @@ -4,9 +4,10 @@ import ( "context" "errors" "fmt" - "github.com/spectrocloud/palette-sdk-go/client" "time" + "github.com/spectrocloud/palette-sdk-go/client" + "github.com/go-openapi/strfmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -140,7 +141,7 @@ func resourceRegistryOciEcr() *schema.Resource { Type: schema.TypeBool, Optional: true, Default: false, - Description: "Disables TLS certificate verification when set to true. Use with caution as it may expose connections to security risks.", + Description: "Disables TLS certificate verification when set to true. ⚠️ WARNING: Setting this to true disables SSL certificate verification and makes connections vulnerable to man-in-the-middle attacks. Only use this when connecting to registries with self-signed certificates in trusted networks.", }, }, }, diff --git a/spectrocloud/resource_sso.go b/spectrocloud/resource_sso.go index 4572edfb3..d67e3c689 100644 --- a/spectrocloud/resource_sso.go +++ b/spectrocloud/resource_sso.go @@ -94,7 +94,7 @@ func resourceSSO() *schema.Resource { Type: schema.TypeBool, Optional: true, Default: false, - Description: "Boolean to skip TLS verification for identity provider communication.", + Description: "Boolean to skip TLS verification for identity provider communication. ⚠️ WARNING: Setting this to true disables SSL certificate verification and makes connections vulnerable to man-in-the-middle attacks. Only use this when connecting to identity providers with self-signed certificates in trusted networks.", }, "client_id": { Type: schema.TypeString, diff --git a/spectrocloud/resource_workspace.go b/spectrocloud/resource_workspace.go index f6115eb58..96923eb43 100644 --- a/spectrocloud/resource_workspace.go +++ b/spectrocloud/resource_workspace.go @@ -96,7 +96,10 @@ func resourceWorkspaceCreate(ctx context.Context, d *schema.ResourceData, m inte var diags diag.Diagnostics - workspace := toWorkspace(d, c) + workspace, err := toWorkspace(d, c) + if err != nil { + return diag.FromErr(err) + } uid, err := c.CreateWorkspace(workspace) if err != nil { @@ -203,7 +206,10 @@ func resourceWorkspaceUpdate(ctx context.Context, d *schema.ResourceData, m inte if d.HasChange("clusters") || d.HasChange("workspace_quota") { // resource allocation should go first because clusters are inside. - namespaces := toUpdateWorkspaceNamespaces(d, c) + namespaces, err := toUpdateWorkspaceNamespaces(d, c) + if err != nil { + return diag.FromErr(err) + } if err := c.UpdateWorkspaceResourceAllocation(d.Id(), namespaces); err != nil { return diag.FromErr(err) } @@ -219,7 +225,11 @@ func resourceWorkspaceUpdate(ctx context.Context, d *schema.ResourceData, m inte } } if d.HasChange("namespaces") { - if err := c.UpdateWorkspaceResourceAllocation(d.Id(), toUpdateWorkspaceNamespaces(d, c)); err != nil { + namespaces, err := toUpdateWorkspaceNamespaces(d, c) + if err != nil { + return diag.FromErr(err) + } + if err := c.UpdateWorkspaceResourceAllocation(d.Id(), namespaces); err != nil { return diag.FromErr(err) } } @@ -269,12 +279,17 @@ func resourceWorkspaceDelete(ctx context.Context, d *schema.ResourceData, m inte return diags } -func toWorkspace(d *schema.ResourceData, c *client.V1Client) *models.V1WorkspaceEntity { +func toWorkspace(d *schema.ResourceData, c *client.V1Client) (*models.V1WorkspaceEntity, error) { annotations := make(map[string]string) if len(d.Get("description").(string)) > 0 { annotations["description"] = d.Get("description").(string) } + quota, err := toQuota(d) + if err != nil { + return nil, err + } + workspace := &models.V1WorkspaceEntity{ Metadata: &models.V1ObjectMeta{ Name: d.Get("name").(string), @@ -287,11 +302,11 @@ func toWorkspace(d *schema.ResourceData, c *client.V1Client) *models.V1Workspace ClusterRbacs: toWorkspaceRBACs(d), ClusterRefs: toClusterRefs(d, c), Policies: toWorkspacePolicies(d), - Quota: toQuota(d), + Quota: quota, }, } - return workspace + return workspace, nil } func resourceWorkspaceImport(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { diff --git a/spectrocloud/resource_workspace_test.go b/spectrocloud/resource_workspace_test.go index 025522dd7..31f1a8a90 100644 --- a/spectrocloud/resource_workspace_test.go +++ b/spectrocloud/resource_workspace_test.go @@ -91,7 +91,8 @@ func TestToWorkspace(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // Initialize resource data with input d := schema.TestResourceDataRaw(t, resourceWorkspace().Schema, tt.input) - result := toWorkspace(d, nil) // nil client for unit test + result, err := toWorkspace(d, nil) // nil client for unit test + assert.NoError(t, err) // Compare the expected and actual result assert.Equal(t, tt.expected.Metadata.Name, result.Metadata.Name) diff --git a/spectrocloud/utils.go b/spectrocloud/utils.go index 840893057..c33225368 100644 --- a/spectrocloud/utils.go +++ b/spectrocloud/utils.go @@ -1,6 +1,37 @@ package spectrocloud -import "time" +import ( + "math" + "time" +) + +// SafeInt32 converts int to int32 with bounds checking to prevent overflow +func SafeInt32(value int) int32 { + if value > math.MaxInt32 { + return math.MaxInt32 + } + if value < math.MinInt32 { + return math.MinInt32 + } + return int32(value) +} + +// SafeInt64 converts int to int64 with bounds checking to prevent overflow +func SafeInt64(value int) int64 { + + return int64(value) +} + +// SafeUint32 converts int to uint32 with bounds checking to prevent overflow +func SafeUint32(value int) uint32 { + if value < 0 { + return 0 + } + if value > math.MaxUint32 { + return math.MaxUint32 + } + return uint32(value) +} func expandStringList(configured []interface{}) []string { vs := make([]string, 0) diff --git a/spectrocloud/workspace_namespace.go b/spectrocloud/workspace_namespace.go index b1db7ad54..74173a0a3 100644 --- a/spectrocloud/workspace_namespace.go +++ b/spectrocloud/workspace_namespace.go @@ -1,6 +1,7 @@ package spectrocloud import ( + "fmt" "math" "regexp" "strconv" @@ -9,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/spectrocloud/palette-sdk-go/api/models" "github.com/spectrocloud/palette-sdk-go/client" + "github.com/spectrocloud/terraform-provider-spectrocloud/spectrocloud/constants" ) // Helper function to create V1WorkspaceResourceAllocation from resource allocation map @@ -29,18 +31,19 @@ func toWorkspaceResourceAllocation(resourceAllocation map[string]interface{}) (* } // Handle GPU configuration if specified - if gpuLimit, exists := resourceAllocation["gpu_limit"]; exists && gpuLimit.(string) != "" { - gpuVal, err := strconv.Atoi(gpuLimit.(string)) - if err == nil && gpuVal > 0 { - provider := "nvidia" // Default provider for cluster allocations - // gpu_provider is optional - mainly used for default resource allocations - if gpuProvider, providerExists := resourceAllocation["gpu_provider"]; providerExists && gpuProvider.(string) != "" { - provider = gpuProvider.(string) - } - resource_alloc.GpuConfig = &models.V1GpuConfig{ - Limit: int32(gpuVal), - Provider: &provider, - } + if gpuVal, exists := resourceAllocation["gpu"]; exists && gpuVal.(int) > 0 { + gpuInt := gpuVal.(int) + if gpuInt > constants.Int32MaxValue { + return nil, fmt.Errorf("gpu value %d is out of range for int32", gpuInt) + } + provider := "nvidia" // Default provider for cluster allocations + // gpu_provider is optional - mainly used for default resource allocations + if gpuProvider, providerExists := resourceAllocation["gpu_provider"]; providerExists && gpuProvider.(string) != "" { + provider = gpuProvider.(string) + } + resource_alloc.GpuConfig = &models.V1GpuConfig{ + Limit: SafeInt32(gpuInt), + Provider: &provider, } } @@ -140,12 +143,17 @@ func IsRegex(name string) bool { } -func toUpdateWorkspaceNamespaces(d *schema.ResourceData, c *client.V1Client) *models.V1WorkspaceClusterNamespacesEntity { +func toUpdateWorkspaceNamespaces(d *schema.ResourceData, c *client.V1Client) (*models.V1WorkspaceClusterNamespacesEntity, error) { + quota, err := toQuota(d) + if err != nil { + return nil, err + } + return &models.V1WorkspaceClusterNamespacesEntity{ ClusterNamespaces: toWorkspaceNamespaces(d), ClusterRefs: toClusterRefs(d, c), - Quota: toQuota(d), - } + Quota: quota, + }, nil } // Helper function to flatten V1WorkspaceResourceAllocation to resource allocation map @@ -153,12 +161,34 @@ func toUpdateWorkspaceNamespaces(d *schema.ResourceData, c *client.V1Client) *mo func flattenWorkspaceResourceAllocation(resourceAlloc *models.V1WorkspaceResourceAllocation, includeProvider bool) map[string]interface{} { result := make(map[string]interface{}) - result["cpu_cores"] = strconv.Itoa(int(math.Round(resourceAlloc.CPUCores))) - result["memory_MiB"] = strconv.Itoa(int(math.Round(resourceAlloc.MemoryMiB))) + // Convert CPU cores with bounds checking to prevent integer overflow + cpuCoresRounded := math.Round(resourceAlloc.CPUCores) + if cpuCoresRounded > math.MaxInt || cpuCoresRounded < math.MinInt { + // Fallback to string representation if out of int range + result["cpu_cores"] = fmt.Sprintf("%.0f", cpuCoresRounded) + } else { + result["cpu_cores"] = strconv.Itoa(int(cpuCoresRounded)) + } + + // Convert memory with bounds checking to prevent integer overflow + memoryMiBRounded := math.Round(resourceAlloc.MemoryMiB) + if memoryMiBRounded > math.MaxInt || memoryMiBRounded < math.MinInt { + // Fallback to string representation if out of int range + result["memory_MiB"] = fmt.Sprintf("%.0f", memoryMiBRounded) + } else { + result["memory_MiB"] = strconv.Itoa(int(memoryMiBRounded)) + } // Handle GPU configuration if present if resourceAlloc.GpuConfig != nil { - result["gpu_limit"] = strconv.Itoa(int(resourceAlloc.GpuConfig.Limit)) + // Convert GPU limit with bounds checking to prevent integer overflow + gpuLimit := int64(resourceAlloc.GpuConfig.Limit) + if gpuLimit > math.MaxInt || gpuLimit < math.MinInt { + // Fallback to string representation if out of int range + result["gpu_limit"] = fmt.Sprintf("%d", gpuLimit) + } else { + result["gpu_limit"] = strconv.Itoa(int(gpuLimit)) + } // Only include gpu_provider for default resource allocations, not cluster-specific ones if includeProvider { if resourceAlloc.GpuConfig.Provider != nil { diff --git a/spectrocloud/workspace_rbac.go b/spectrocloud/workspace_rbac.go index 1df443021..a7952d60a 100644 --- a/spectrocloud/workspace_rbac.go +++ b/spectrocloud/workspace_rbac.go @@ -1,8 +1,11 @@ package spectrocloud import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/spectrocloud/palette-sdk-go/api/models" + "github.com/spectrocloud/terraform-provider-spectrocloud/spectrocloud/constants" ) func toWorkspaceRBACs(d *schema.ResourceData) []*models.V1ClusterRbac { @@ -18,7 +21,7 @@ func toWorkspaceRBACs(d *schema.ResourceData) []*models.V1ClusterRbac { return workspace_rbacs } -func toQuota(d *schema.ResourceData) *models.V1WorkspaceQuota { +func toQuota(d *schema.ResourceData) (*models.V1WorkspaceQuota, error) { wsQuota, ok := d.GetOk("workspace_quota") if !ok || len(wsQuota.([]interface{})) == 0 { return &models.V1WorkspaceQuota{ @@ -26,7 +29,7 @@ func toQuota(d *schema.ResourceData) *models.V1WorkspaceQuota { CPUCores: 0, MemoryMiB: 0, }, - } + }, nil } q := wsQuota.([]interface{})[0].(map[string]interface{}) @@ -37,14 +40,18 @@ func toQuota(d *schema.ResourceData) *models.V1WorkspaceQuota { // Handle GPU configuration if specified if gpuVal, exists := q["gpu"]; exists && gpuVal.(int) > 0 { + gpuInt := gpuVal.(int) + if gpuInt > constants.Int32MaxValue { + return nil, fmt.Errorf("gpu value %d is out of range for int32", gpuInt) + } provider := "nvidia" // Default to nvidia as it's the only supported provider resourceAllocation.GpuConfig = &models.V1GpuConfig{ - Limit: int32(gpuVal.(int)), + Limit: SafeInt32(gpuInt), Provider: &provider, } } return &models.V1WorkspaceQuota{ ResourceAllocation: resourceAllocation, - } + }, nil } diff --git a/tests/mockApiServer/apiServerMock.go b/tests/mockApiServer/apiServerMock.go index b62738676..c7f2f54ce 100644 --- a/tests/mockApiServer/apiServerMock.go +++ b/tests/mockApiServer/apiServerMock.go @@ -4,6 +4,7 @@ import ( "encoding/json" "log" "net/http" + "time" "github.com/gorilla/mux" "github.com/spectrocloud/terraform-provider-spectrocloud/tests/mockApiServer/routes" @@ -45,15 +46,29 @@ func main() { // Start servers on different ports go func() { log.Println("Starting server on :8088...") - if err := http.ListenAndServeTLS(":8088", "mock_server.crt", "mock_server.key", router8088); err != nil { + server := &http.Server{ + Addr: ":8088", + Handler: router8088, + ReadTimeout: 15 * time.Second, + WriteTimeout: 15 * time.Second, + IdleTimeout: 60 * time.Second, + } + if err := server.ListenAndServeTLS("mock_server.crt", "mock_server.key"); err != nil { log.Fatalf("Server failed to start on port 8088: %v", err) } }() log.Println("Starting server on :8888...") - if err := http.ListenAndServeTLS(":8888", "mock_server.crt", "mock_server.key", router8888); err != nil { - log.Fatalf("Server failed to start on port 8088: %v", err) + server := &http.Server{ + Addr: ":8888", + Handler: router8888, + ReadTimeout: 15 * time.Second, + WriteTimeout: 15 * time.Second, + IdleTimeout: 60 * time.Second, + } + if err := server.ListenAndServeTLS("mock_server.crt", "mock_server.key"); err != nil { + log.Fatalf("Server failed to start on port 8888: %v", err) } }