mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-05 20:40:16 -08:00
feat(java): dereference all maven settings.xml env placeholders (#9024)
This commit is contained in:
@@ -2,9 +2,6 @@ package pom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -17,7 +14,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
varRegexp = regexp.MustCompile(`\${(\S+?)}`)
|
||||
emptyVersionWarn = sync.OnceFunc(func() {
|
||||
log.WithPrefix("pom").Warn("Dependency version cannot be determined. Child dependencies will not be found.",
|
||||
// e.g. https://trivy.dev/latest/docs/coverage/language/java/#empty-dependency-version
|
||||
@@ -133,44 +129,3 @@ func (v1 version) shouldOverride(v2 version) bool {
|
||||
func (v1 version) String() string {
|
||||
return v1.ver
|
||||
}
|
||||
|
||||
func evaluateVariable(s string, props map[string]string, seenProps []string) string {
|
||||
if props == nil {
|
||||
props = make(map[string]string)
|
||||
}
|
||||
|
||||
for _, m := range varRegexp.FindAllStringSubmatch(s, -1) {
|
||||
var newValue string
|
||||
|
||||
// env.X: https://maven.apache.org/pom.html#Properties
|
||||
// e.g. env.PATH
|
||||
if strings.HasPrefix(m[1], "env.") {
|
||||
newValue = os.Getenv(strings.TrimPrefix(m[1], "env."))
|
||||
} else {
|
||||
// <properties> might include another property.
|
||||
// e.g. <animal.sniffer.skip>${skipTests}</animal.sniffer.skip>
|
||||
ss, ok := props[m[1]]
|
||||
if ok {
|
||||
// search for looped properties
|
||||
if slices.Contains(seenProps, ss) {
|
||||
printLoopedPropertiesStack(m[0], seenProps)
|
||||
return ""
|
||||
}
|
||||
seenProps = append(seenProps, ss) // save evaluated props to check if we get this prop again
|
||||
newValue = evaluateVariable(ss, props, seenProps)
|
||||
seenProps = []string{} // clear props if we returned from recursive. Required for correct work with 2 same props like ${foo}-${foo}
|
||||
}
|
||||
|
||||
}
|
||||
s = strings.ReplaceAll(s, m[0], newValue)
|
||||
}
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
func printLoopedPropertiesStack(env string, usedProps []string) {
|
||||
var s string
|
||||
for _, prop := range usedProps {
|
||||
s += fmt.Sprintf("%s -> ", prop)
|
||||
}
|
||||
log.Warn("Lopped properties were detected", log.String("prop", s+env))
|
||||
}
|
||||
|
||||
@@ -832,3 +832,11 @@ func packageID(name, version string) string {
|
||||
func isSnapshot(ver string) bool {
|
||||
return strings.HasSuffix(ver, "SNAPSHOT") || ver == "LATEST"
|
||||
}
|
||||
|
||||
func isDirectory(path string) (bool, error) {
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return fileInfo.IsDir(), err
|
||||
}
|
||||
|
||||
@@ -76,5 +76,17 @@ func openSettings(filePath string) (settings, error) {
|
||||
if err = decoder.Decode(&s); err != nil {
|
||||
return settings{}, err
|
||||
}
|
||||
|
||||
expandAllEnvPlaceholders(&s)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func expandAllEnvPlaceholders(s *settings) {
|
||||
s.LocalRepository = evaluateVariable(s.LocalRepository, nil, nil)
|
||||
for i, server := range s.Servers {
|
||||
s.Servers[i].ID = evaluateVariable(server.ID, nil, nil)
|
||||
s.Servers[i].Username = evaluateVariable(server.Username, nil, nil)
|
||||
s.Servers[i].Password = evaluateVariable(server.Password, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,35 @@ func Test_ReadSettings(t *testing.T) {
|
||||
},
|
||||
wantSettings: settings{},
|
||||
},
|
||||
{
|
||||
name: "environment placeholders are dereferenced",
|
||||
envs: map[string]string{
|
||||
"HOME": filepath.Join("testdata", "settings", "user-settings-with-env-placeholders"),
|
||||
"MAVEN_HOME": "NOT_EXISTING_PATH",
|
||||
"LOCAL_REPO_PT_1": "part1",
|
||||
"LOCAL_REPO_PT_2": "part2",
|
||||
"SERVER_ID": "server-id-from-env",
|
||||
"USERNAME": "username-from-env",
|
||||
"PASSWORD": "password-from-env",
|
||||
},
|
||||
wantSettings: settings{
|
||||
LocalRepository: "part1/part2/.m2/repository",
|
||||
Servers: []Server{
|
||||
{
|
||||
ID: "user-server",
|
||||
},
|
||||
{
|
||||
ID: "server-id-from-env",
|
||||
Username: "username-from-env",
|
||||
Password: "password-from-env",
|
||||
},
|
||||
{
|
||||
ID: "server-with-name-only",
|
||||
Username: "test-user-only",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
21
pkg/dependency/parser/java/pom/testdata/settings/user-settings-with-env-placeholders/.m2/settings.xml
vendored
Executable file
21
pkg/dependency/parser/java/pom/testdata/settings/user-settings-with-env-placeholders/.m2/settings.xml
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
<localRepository>${env.LOCAL_REPO_PT_1}/${env.LOCAL_REPO_PT_2}/.m2/repository</localRepository>
|
||||
|
||||
<servers>
|
||||
<server>
|
||||
<id>user-server</id>
|
||||
</server>
|
||||
<server>
|
||||
<id>${env.SERVER_ID}</id>
|
||||
<username>${env.USERNAME}</username>
|
||||
<password>${env.PASSWORD}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>server-with-name-only</id>
|
||||
<username>test-user-only</username>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
||||
@@ -1,21 +0,0 @@
|
||||
package pom
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func isDirectory(path string) (bool, error) {
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return fileInfo.IsDir(), err
|
||||
}
|
||||
|
||||
func isProperty(version string) bool {
|
||||
if version != "" && strings.HasPrefix(version, "${") && strings.HasSuffix(version, "}") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
61
pkg/dependency/parser/java/pom/var.go
Normal file
61
pkg/dependency/parser/java/pom/var.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package pom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
)
|
||||
|
||||
var varRegexp = regexp.MustCompile(`\${(\S+?)}`)
|
||||
|
||||
func isProperty(s string) bool {
|
||||
if s != "" && strings.HasPrefix(s, "${") && strings.HasSuffix(s, "}") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func evaluateVariable(s string, props map[string]string, seenProps []string) string {
|
||||
if props == nil {
|
||||
props = make(map[string]string)
|
||||
}
|
||||
|
||||
for _, m := range varRegexp.FindAllStringSubmatch(s, -1) {
|
||||
var newValue string
|
||||
|
||||
// env.X: https://maven.apache.org/pom.html#Properties
|
||||
// e.g. env.PATH
|
||||
if strings.HasPrefix(m[1], "env.") {
|
||||
newValue = os.Getenv(strings.TrimPrefix(m[1], "env."))
|
||||
} else {
|
||||
// <properties> might include another property.
|
||||
// e.g. <animal.sniffer.skip>${skipTests}</animal.sniffer.skip>
|
||||
ss, ok := props[m[1]]
|
||||
if ok {
|
||||
// search for looped properties
|
||||
if slices.Contains(seenProps, ss) {
|
||||
printLoopedPropertiesStack(m[0], seenProps)
|
||||
return ""
|
||||
}
|
||||
seenProps = append(seenProps, ss) // save evaluated props to check if we get this prop again
|
||||
newValue = evaluateVariable(ss, props, seenProps)
|
||||
seenProps = []string{} // clear props if we returned from recursive. Required for correct work with 2 same props like ${foo}-${foo}
|
||||
}
|
||||
|
||||
}
|
||||
s = strings.ReplaceAll(s, m[0], newValue)
|
||||
}
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
func printLoopedPropertiesStack(env string, usedProps []string) {
|
||||
var s string
|
||||
for _, prop := range usedProps {
|
||||
s += fmt.Sprintf("%s -> ", prop)
|
||||
}
|
||||
log.Warn("Lopped properties were detected", log.String("prop", s+env))
|
||||
}
|
||||
Reference in New Issue
Block a user