Skip to content

Commit

Permalink
fix: validate existence and structure of credentials_file (#322)
Browse files Browse the repository at this point in the history
- Added checks for the existence of the credentials_file provided by the user.
- Validated the correct data structure of the token file.
- Implemented a file size limit check to prevent issues with excessively large files.
  • Loading branch information
Praveen005 authored Aug 15, 2024
1 parent a8a06e9 commit cbb74fb
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 26 deletions.
62 changes: 39 additions & 23 deletions civo/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import (
"github.com/civo/terraform-provider-civo/civo/size"
"github.com/civo/terraform-provider-civo/civo/ssh"
"github.com/civo/terraform-provider-civo/civo/volume"
"github.com/civo/terraform-provider-civo/internal/utils"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mitchellh/go-homedir"
)

var (
Expand Down Expand Up @@ -121,11 +123,11 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
regionValue = region.(string)
}

if token, ok, source := getToken(d); ok {
if token, source, err := getToken(d); err == nil {
tokenValue = token.(string)
tokenSource = source
} else {
return nil, fmt.Errorf("[ERR] No token configuration found in $CIVO_TOKEN or ~/.civo.json. Please go to https://dashboard.civo.com/security to fetch one")
return nil, fmt.Errorf("[ERR] No token configuration found in $CIVO_TOKEN or credentials_file or ~/.civo.json. Please go to https://dashboard.civo.com/security to fetch one: %v", err)
}

if apiEndpoint, ok := d.GetOk("api_endpoint"); ok {
Expand Down Expand Up @@ -160,48 +162,50 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
return client, nil
}

func getToken(d *schema.ResourceData) (interface{}, bool, string) {
var exists = true
func getToken(d *schema.ResourceData) (interface{}, string, error) {

// Gets you the token atrribute value or falls back to reading CIVO_TOKEN environment variable
if token, ok := d.GetOk("token"); ok {
return token, exists, "environment variable"
return token, "environment variable", nil
}

// Check for credentials file specified in provider config
if credFile, ok := d.GetOk("credentials_file"); ok {
token, err := readTokenFromFile(credFile.(string))
path, err := homedir.Expand(credFile.(string))
if err != nil {
return nil, "", fmt.Errorf("error expanding %v: %w", credFile, err)
}
token, err := readTokenFromFile(path)
if err == nil {
return token, exists, "credentials file"
return token, "credentials file", nil
}
return nil, "", fmt.Errorf("error reading from credentials_file: %v", err)
}

// Check for default CLI config file
homeDir, err := getHomeDir()
homeDir, err := homedir.Dir()
if err == nil {
token, err := readTokenFromFile(filepath.Join(homeDir, ".civo.json"))
if err == nil {
return token, exists, "CLI config file"
return token, "CLI config file", nil
}
return nil, "", fmt.Errorf("error reading from ~/.civo.json: %v", err)
}

return nil, !exists, ""
return nil, "", err

}

var getHomeDir = func() (string, error) {
if home := os.Getenv("HOME"); home != "" {
return home, nil
func readTokenFromFile(path string) (string, error) {
// Check file size: 20 MB limit
if err := utils.CheckFileSize(path); err != nil {
return "", err
}
// Fall back to os.UserHomeDir() if HOME is not set
return os.UserHomeDir()
}

func readTokenFromFile(path string) (string, error) {
// read the file
data, err := os.ReadFile(path)

if err != nil {
return "", err
return "", fmt.Errorf("error reading file (%s): %w", path, err)
}

var config struct {
Expand All @@ -211,20 +215,32 @@ func readTokenFromFile(path string) (string, error) {
} `json:"meta"`
}

exampleJSON := `
{
"apikeys": {
"tf_key": "token_here"
},
"meta": {
"current_apikey": "tf_key"
}
}`

if err := json.Unmarshal(data, &config); err != nil {
return "", err
return "", fmt.Errorf("failed to parse JSON from '%s': %w. Please ensure the input JSON file is correctly formatted and all required fields are present. Expected format:\n%s", path, err, exampleJSON)
}

if config.APIKeys == nil || config.Meta.CurrentAPIKey == "" {
return "", fmt.Errorf("invalid structure in '%s', missing required fields. Expected format:\n%s", path, exampleJSON)
}

// Get the current API key name
currentKeyName := config.Meta.CurrentAPIKey

// Fetch the corresponding token
token, ok := config.APIKeys[currentKeyName]

if !ok {
return "", fmt.Errorf("API key '%s' not found", currentKeyName)
return "", fmt.Errorf("API key '%s' not found in '%s'", currentKeyName, path)
}

return token, nil
}

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ require (
github.com/civo/civogo v0.3.73
github.com/google/uuid v1.3.1
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.31.0
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.25.0
Expand All @@ -31,6 +30,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hc-install v0.6.2 // indirect
github.com/hashicorp/hcl/v2 v2.19.1 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
Expand All @@ -45,6 +45,7 @@ require (
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.6.2 h1:V1k+Vraqz4olgZ9UzKiAcbman9i9scg9GgSt/U3mw/M=
github.com/hashicorp/hc-install v0.6.2/go.mod h1:2JBpd+NCFKiHiu/yYCGaPyPHhZLxXTpz8oreHa/a3Ps=
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
Expand Down Expand Up @@ -128,6 +128,8 @@ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPn
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
Expand Down
23 changes: 23 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ package utils

import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"log"
"os"
"os/exec"
"regexp"
"sort"
Expand All @@ -22,6 +25,11 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
)

const (
// FileSizeLimit limits the size of file to be used by the user
FileSizeLimit = int64(20 * 1024 * 1024) // 20 MB
)

// VersionInfo stores Provider's version Info
type VersionInfo struct {
ProviderSelections map[string]string `json:"provider_selections"`
Expand Down Expand Up @@ -317,3 +325,18 @@ func ValidateUUID(v interface{}, k string) (ws []string, errors []error) {
}
return
}

// CheckFileSize function checks if the file the file size is less than the allowed limit(current: 20MB)
func CheckFileSize(path string) error {
fileInfo, err := os.Stat(path)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("file does not exist: %s", path)
}
return fmt.Errorf("error getting the file info: %w", err)
}
if fileInfo.Size() > FileSizeLimit {
return fmt.Errorf("file size exceeds the allowed limit of %d bytes", FileSizeLimit)
}
return nil
}

0 comments on commit cbb74fb

Please sign in to comment.