github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/scanners/terraform/parser/resolvers/registry.go (about) 1 package resolvers 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io/fs" 8 "net/http" 9 "os" 10 "sort" 11 "strings" 12 "time" 13 14 "github.com/Masterminds/semver" 15 ) 16 17 type registryResolver struct { 18 client *http.Client 19 } 20 21 var Registry = ®istryResolver{ 22 client: &http.Client{ 23 // give it a maximum 5 seconds to resolve the module 24 Timeout: time.Second * 5, 25 }, 26 } 27 28 type moduleVersions struct { 29 Modules []struct { 30 Versions []struct { 31 Version string `json:"version"` 32 } `json:"versions"` 33 } `json:"modules"` 34 } 35 36 const registryHostname = "registry.terraform.io" 37 38 // nolint 39 func (r *registryResolver) Resolve(ctx context.Context, target fs.FS, opt Options) (filesystem fs.FS, prefix string, downloadPath string, applies bool, err error) { 40 41 if !opt.AllowDownloads { 42 return 43 } 44 45 inputVersion := opt.Version 46 source, relativePath, _ := strings.Cut(opt.Source, "//") 47 parts := strings.Split(source, "/") 48 if len(parts) < 3 || len(parts) > 4 { 49 return 50 } 51 52 hostname := registryHostname 53 var token string 54 if len(parts) == 4 { 55 hostname = parts[0] 56 parts = parts[1:] 57 58 envVar := fmt.Sprintf("TF_TOKEN_%s", strings.ReplaceAll(hostname, ".", "_")) 59 token = os.Getenv(envVar) 60 if token != "" { 61 opt.Debug("Found a token for the registry at %s", hostname) 62 } else { 63 opt.Debug("No token was found for the registry at %s", hostname) 64 } 65 } 66 67 moduleName := strings.Join(parts, "/") 68 69 if opt.Version != "" { 70 versionUrl := fmt.Sprintf("https://%s/v1/modules/%s/versions", hostname, moduleName) 71 opt.Debug("Requesting module versions from registry using '%s'...", versionUrl) 72 req, err := http.NewRequestWithContext(ctx, http.MethodGet, versionUrl, nil) 73 if err != nil { 74 return nil, "", "", true, err 75 } 76 if token != "" { 77 req.Header.Set("Authorization", "Bearer "+token) 78 } 79 resp, err := r.client.Do(req) 80 if err != nil { 81 return nil, "", "", true, err 82 } 83 defer func() { _ = resp.Body.Close() }() 84 if resp.StatusCode != http.StatusOK { 85 return nil, "", "", true, fmt.Errorf("unexpected status code for versions endpoint: %d", resp.StatusCode) 86 } 87 var availableVersions moduleVersions 88 if err := json.NewDecoder(resp.Body).Decode(&availableVersions); err != nil { 89 return nil, "", "", true, err 90 } 91 92 opt.Version, err = resolveVersion(inputVersion, availableVersions) 93 if err != nil { 94 return nil, "", "", true, err 95 } 96 opt.Debug("Found version '%s' for constraint '%s'", opt.Version, inputVersion) 97 } 98 99 var url string 100 if opt.Version == "" { 101 url = fmt.Sprintf("https://%s/v1/modules/%s/download", hostname, moduleName) 102 } else { 103 url = fmt.Sprintf("https://%s/v1/modules/%s/%s/download", hostname, moduleName, opt.Version) 104 } 105 106 opt.Debug("Requesting module source from registry using '%s'...", url) 107 108 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 109 if err != nil { 110 return nil, "", "", true, err 111 } 112 if token != "" { 113 req.Header.Set("Authorization", "Bearer "+token) 114 } 115 if opt.Version != "" { 116 req.Header.Set("X-Terraform-Version", opt.Version) 117 } 118 119 resp, err := r.client.Do(req) 120 if err != nil { 121 return nil, "", "", true, err 122 } 123 defer func() { _ = resp.Body.Close() }() 124 if resp.StatusCode != http.StatusNoContent { 125 return nil, "", "", true, fmt.Errorf("unexpected status code: %d", resp.StatusCode) 126 } 127 128 opt.Source = resp.Header.Get("X-Terraform-Get") 129 opt.Debug("Module '%s' resolved via registry to new source: '%s'", opt.Name, opt.Source) 130 opt.RelativePath = relativePath 131 filesystem, prefix, downloadPath, _, err = Remote.Resolve(ctx, target, opt) 132 if err != nil { 133 return nil, "", "", true, err 134 } 135 136 return filesystem, prefix, downloadPath, true, nil 137 } 138 139 func resolveVersion(input string, versions moduleVersions) (string, error) { 140 if len(versions.Modules) != 1 { 141 return "", fmt.Errorf("1 module expected, found %d", len(versions.Modules)) 142 } 143 if len(versions.Modules[0].Versions) == 0 { 144 return "", fmt.Errorf("no available versions for module") 145 } 146 constraints, err := semver.NewConstraint(input) 147 if err != nil { 148 return "", err 149 } 150 var realVersions semver.Collection 151 for _, rawVersion := range versions.Modules[0].Versions { 152 realVersion, err := semver.NewVersion(rawVersion.Version) 153 if err != nil { 154 continue 155 } 156 realVersions = append(realVersions, realVersion) 157 } 158 sort.Sort(sort.Reverse(realVersions)) 159 for _, realVersion := range realVersions { 160 if constraints.Check(realVersion) { 161 return realVersion.String(), nil 162 } 163 } 164 return "", fmt.Errorf("no available versions for module constraint '%s'", input) 165 }