Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 25 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ selected branch (in variable REPO_BRANCH) and can be pulled new version on defin

## RELEASE NOTES: v0.0.3-alpha

| Feature | Description |
|---------|---------------------------------------------------|
| Done | Add support to proxy redirect with HTTPS backends |
| FIX | Bump golang.org/x/text from 0.3.7 to 0.3.8 |

| Feature | Description |
|---------|--------------------------------------------------------------------------------------------------------------------|
| Done | Add support to proxy redirect with HTTPS backends |
| FIXED | Bump golang.org/x/text from 0.3.7 to 0.3.8 |
| FIXED | Bump github.com/stretchr/testify from 1.7.0 to 1.7.1 |
| FIXED | Bump golang.org/x/sys from 0.0.0-20220209214540-3681064d5158 to 0.1.0 |
| FIXED | Bump golang.org/x/crypto from 0.0.0-20220214200702-86341886e292 to 0.1.0 |
| FIXED | Bump golang.org/x/net from 0.0.0-20220127200216-cd36cc0744dd to 0.17.0 in |
| FIXED | Vulnerability issue Uncontrolled data used in path [Issue](https://github.com/jarpsimoes/git-http-server/issues/8) |


## Authentication Methods
Expand All @@ -25,22 +29,22 @@ The GIT-HttpServer only support basic authentication on repositories by protocol
## Configuration

### Environment Variables
| Name | Description | Default | Mandatory |
|---------------------------|----------------------------------------------------------|---------------------------------------------------|-----------|
| PATH_CLONE | Set clone path | _clone | Yes |
| PATH_PULL | Set pull path | _pull | Yes |
| PATH_VERSION | Set get git commit version path | _version | Yes |
| PATH_WEBHOOK | Set webhook path | _hook | Yes |
| PATH_HEALTH | Set health check path | _health | Yes |
| REPO_BRANCH | Set default branch to clone content | main | No |
| REPO_TARGET_FOLDER | Set folder to clone source | target-git | No |
| REPO_URL | Set url as a source origin | https://github.com/jarpsimoes/git-http-server.git | No |
| REPO_USERNAME | Set username or token identifier to basic authentication | N/D | No |
| REPO_PASSWORD | Set password or token to basic authentication | N/D | No |
| HTTP_PORT | Set port to expose content | 8081 | Yes |
| GHS_CUSTOM_PATH_<path> | Custom path to work as a proxy server | N/D | No |
| GHS_CUSTOM_REWRITE_<path> | Set to remove from proxy request base path | N/D | No |
| FOLDER_ROOT | Select root folder inside cloned repository | $REPO_TARGET_FOLDER/ | No |
| Name | Description | Default | Mandatory |
|---------------------------|------------------------------------------------------------------------|---------------------------------------------------|-----------|
| PATH_CLONE | Set clone path | _clone | Yes |
| PATH_PULL | Set pull path | _pull | Yes |
| PATH_VERSION | Set get git commit version path | _version | Yes |
| PATH_WEBHOOK | Set webhook path | _hook | Yes |
| PATH_HEALTH | Set health check path | _health | Yes |
| REPO_BRANCH | Set default branch to clone content | main | No |
| REPO_TARGET_FOLDER | Set folder to clone source (only allowed letters, numbers, underscore) | target-git | No |
| REPO_URL | Set url as a source origin | https://github.com/jarpsimoes/git-http-server.git | No |
| REPO_USERNAME | Set username or token identifier to basic authentication | N/D | No |
| REPO_PASSWORD | Set password or token to basic authentication | N/D | No |
| HTTP_PORT | Set port to expose content | 8081 | Yes |
| GHS_CUSTOM_PATH_<path> | Custom path to work as a proxy server | N/D | No |
| GHS_CUSTOM_REWRITE_<path> | Set to remove from proxy request base path | N/D | No |
| FOLDER_ROOT **(Removed)** | The base path will be always root folder of the application | REMOVED | N/D |


## Implementation
Expand Down
34 changes: 32 additions & 2 deletions src/utils/custom_path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import (
func TestGetAllCustomPaths(t *testing.T) {
os.Setenv("GHS_CUSTOM_PATH_images/digilex-infordoc-images", "https://storage.gra.cloud.ovh.net/v1/AUTH_ed33ec9e34c64b54aca49d6fcb6dc4c8/infordoc-img")
os.Setenv("GHS_CUSTOM_REWRITE_images/digilex-infordoc-images", "")
os.Setenv("GHS_CUSTOM_PATH_images.digilex-infordoc-images-1", "https://storage.gra.cloud.ovh.net/v1/AUTH_ed33ec9e34c64b54aca49d6fcb6dc4c8/img")
os.Setenv("GHS_CUSTOM_PATH_/images/digilex-infordoc-images-2", "https://storage.gra.cloud.ovh.net/v1/AUTH_ed33ec9e34c64b54aca49d6fcb6dc4c8/infordoc")
os.Setenv("GHS_CUSTOM_PATH_images.digilex-infordoc/1", "https://storage.gra.cloud.ovh.net/v1/AUTH_ed33ec9e34c64b54aca49d6fcb6dc4c8/img")
os.Setenv("GHS_CUSTOM_PATH_/images/digilex-infordoc-img", "https://storage.gra.cloud.ovh.net/v1/AUTH_ed33ec9e34c64b54aca49d6fcb6dc4c8/infordoc")

result := GetCustomPathsInstance()

assert.True(t, len(*result) == 3)
Expand All @@ -25,3 +26,32 @@ func TestGetAllCustomPaths(t *testing.T) {
}

}
func TestFindPath(t *testing.T) {
os.Setenv("GHS_CUSTOM_PATH_images/digilex-infordoc-images", "https://storage.gra.cloud.ovh.net/v1/AUTH_ed33ec9e34c64b54aca49d6fcb6dc4c8/infordoc-img")
os.Setenv("GHS_CUSTOM_REWRITE_images/digilex-infordoc-images", "")
os.Setenv("GHS_CUSTOM_PATH_images.digilex-infordoc/1", "https://storage.gra.cloud.ovh.net/v1/AUTH_ed33ec9e34c64b54aca49d6fcb6dc4c8/img")
os.Setenv("GHS_CUSTOM_PATH_/images/digilex-infordoc-img", "https://storage.gra.cloud.ovh.net/v1/AUTH_ed33ec9e34c64b54aca49d6fcb6dc4c8/infordoc")

result, path := FindPath("/images/digilex-infordoc-images")

assert.True(t, result)
assert.True(t, path.GetTarget() == "https://storage.gra.cloud.ovh.net/v1/AUTH_ed33ec9e34c64b54aca49d6fcb6dc4c8/infordoc-img")
assert.True(t, path.IsRewrite())

result1, path1 := FindPath("/images/digilex-infordoc/1")

assert.True(t, result1)
assert.True(t, path1.GetTarget() == "https://storage.gra.cloud.ovh.net/v1/AUTH_ed33ec9e34c64b54aca49d6fcb6dc4c8/img")
assert.True(t, path1.IsRewrite() == false)

result2, path2 := FindPath("/images/digilex-infordoc-img")

assert.True(t, result2)
assert.True(t, path2.GetTarget() == "https://storage.gra.cloud.ovh.net/v1/AUTH_ed33ec9e34c64b54aca49d6fcb6dc4c8/infordoc")
assert.True(t, path2.IsRewrite() == false)

result3, _ := FindPath("/images/test/digilex-infordoc-img")

assert.False(t, result3)

}
16 changes: 15 additions & 1 deletion src/utils/environment_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/go-git/go-git/v5/plumbing/transport/http"
"log"
"os"
"path/filepath"
"runtime"
"sync"
"time"
)
Expand Down Expand Up @@ -51,6 +53,11 @@ var baseRouteConfigInstance *BaseRouteConfig
var baseRepositoryConfigInstance *BaseRepositoryConfig
var basicAuthenticationMethod *BasicAuthenticationMethod
var healthCheckControl *HealthCheckControl
var pathSecurityCheck *PathSecurityCheck
var (
_, b, _, _ = runtime.Caller(0)
basePath = filepath.Dir(b)
)

// UpdateState [HealthCheckControl] it's a function to update Status
func (hcc *HealthCheckControl) UpdateState(status bool) {
Expand Down Expand Up @@ -183,9 +190,16 @@ func GetRepositoryConfigInstance() *BaseRepositoryConfig {
repoURL: os.Getenv("REPO_URL"),
branch: os.Getenv("REPO_BRANCH"),
targetFolder: os.Getenv("REPO_TARGET_FOLDER"),
rootFolder: os.Getenv("FOLDER_ROOT"),
rootFolder: basePath,
}

if pathSecurityCheck.IsValidPath(baseRepositoryConfigInstance.targetFolder) {
log.Printf("[BaseRepositoryConfigInstance] Target folder %v is valid", baseRepositoryConfigInstance.targetFolder)
} else {
log.Printf("[BaseRepositoryConfigInstance] Target folder %v is invalid", baseRepositoryConfigInstance.targetFolder)
log.Printf("[BaseRepositoryConfigInstance] Will be changed to target")
baseRepositoryConfigInstance.targetFolder = "target_folder"
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/utils/environment_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ func TestGetRepositoryConfigInstance(t *testing.T) {
assert.Equal(t, "https://test.com/repo.git", repositoryConfigInstance1.GetRepo(), "Check repo url")
assert.Equal(t, "main", repositoryConfigInstance1.GetBranch(), "Check repo url")
assert.Equal(t, "test1", repositoryConfigInstance1.GetTargetFolder(), "Check repo url")

}
func TestGetRepositoryConfigInstanceInvalid(t *testing.T) {
baseRepositoryConfigInstance = nil

os.Setenv("REPO_URL", "https://test.com/repo.git")
os.Setenv("REPO_BRANCH", "main")
os.Setenv("REPO_TARGET_FOLDER", "test1/test2")

repositoryConfigInstance2 := GetRepositoryConfigInstance()
assert.Equal(t, "target_folder", repositoryConfigInstance2.GetTargetFolder(), "Check repo url")
}
func TestGetBasicAuthenticationMethodInstance(t *testing.T) {
os.Setenv("REPO_USERNAME", os.Getenv("ACCESS_USERNAME"))
Expand Down
61 changes: 61 additions & 0 deletions src/utils/path_security_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package utils

import (
"regexp"
)

type PathSecurityCheck struct {
}

// IsValidPath checks if the input string adheres to the specified rules.
func (psc *PathSecurityCheck) IsValidPath(input string) bool {
// Rule 1: Do not allow more than a single "." character.
if countDots := psc.countOccurrences(input, '.'); countDots > 1 {
return false
}

if psc.containsDirectorySeparator(input) {
return false
}

allowList := []*regexp.Regexp{
regexp.MustCompile(`^[a-zA-Z0-9_-]+$`), // Alphanumeric characters, underscore, and hyphen.
regexp.MustCompile(`^([a-zA-Z0-9_-]+\.){0,2}[a-zA-Z0-9_-]+$`), // Allow up to two dots in the filename.
}

for _, pattern := range allowList {
if pattern.MatchString(input) {
return true
}
}

return false
}

// countOccurrences counts the occurrences of a specific character in a string.
func (psc *PathSecurityCheck) countOccurrences(s string, c byte) int {
count := 0
for i := 0; i < len(s); i++ {
if s[i] == c {
count++
}
}
return count
}

// containsDirectorySeparator checks if the input string contains directory separators.
func (psc *PathSecurityCheck) containsDirectorySeparator(s string) bool {
return psc.containsAny(s, []byte{'/', '\\'})
}

// containsAny checks if the input string contains any of the specified characters.
func (psc *PathSecurityCheck) containsAny(s string, chars []byte) bool {
for i := 0; i < len(s); i++ {
for _, c := range chars {
if s[i] == c {
return true
}
}
}
return false
}
33 changes: 33 additions & 0 deletions src/utils/path_security_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package utils

import (
"testing"
)

func TestIsValidPath(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{"valid_path123", true},
{"invalid_path/with_slash", false},
{"invalid_path/with/slash", false},
{"invalid/path/with/slash", false},
{"invalid.path.with.multiple.dots", false},
{"another.invalid.path.with.multiple.dots", false},
{"valid_path_with_underscore", true},
{"valid-path_with-hyphen", true},
{"valid.path_with_underscore-hyphen", true},
}

pathSecurityCheck := PathSecurityCheck{}

for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
result := pathSecurityCheck.IsValidPath(test.input)
if result != test.expected {
t.Errorf("Expected IsValidPath(%s) to be %v, but got %v", test.input, test.expected, result)
}
})
}
}