diff --git a/cmd/cli/root.go b/cmd/cli/root.go index ce8e27c..1527fda 100644 --- a/cmd/cli/root.go +++ b/cmd/cli/root.go @@ -1,6 +1,7 @@ package cli import ( + "fmt" "os" "github.com/spf13/cobra" @@ -18,6 +19,7 @@ var rootCmd = &cobra.Command{ func Execute() { err := rootCmd.Execute() if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } } diff --git a/go.mod b/go.mod index 9d19371..bd1d8b8 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( require ( github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/docker/cli v28.1.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect diff --git a/go.sum b/go.sum index 1fa665d..7403c57 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k= github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= diff --git a/internal/docker/fucntion_test.go b/internal/docker/fucntion_test.go index 5945e32..8b8a8f5 100644 --- a/internal/docker/fucntion_test.go +++ b/internal/docker/fucntion_test.go @@ -12,20 +12,28 @@ func TestValidateDockerImageName(t *testing.T) { image string expected bool }{ - // Valid image names + // Valid image names (following Docker and OCI standards) {"Valid image name without registry or tag", "ubuntu", true}, {"Valid image name with namespace", "library/ubuntu", true}, {"Valid image name with registry", "docker.io/library/ubuntu", true}, {"Valid image name with custom registry and port", "localhost:5000/myproject/ubuntu", true}, {"Valid image name with tag", "myregistry/myproject/ubuntu:latest", true}, - {"Valid image name with digest", "myregistry/myproject/ubuntu@sha256:abc123", true}, + {"Valid image name with version tag", "nginx:1.21.0", true}, + {"Valid image name with custom tag", "myapp:v1.0.0-beta", true}, + {"Valid image name with underscore", "my_project/app:latest", true}, + {"Valid image name with multiple slashes", "org/team/project/app:latest", true}, + {"Valid image name with IP address registry", "192.168.1.100:5000/app:latest", true}, // Invalid image names {"Invalid image name with uppercase letters in repository", "MyRegistry/MyProject/Ubuntu", false}, {"Invalid image name with invalid characters", "invalid!@#/image/name", false}, - {"Invalid image name with empty repository", "", false}, + {"Invalid image name with empty string", "", false}, {"Invalid image name with only slashes", "///", false}, {"Invalid image name with multiple @ symbols", "invalid@image@name", false}, + {"Invalid image name with invalid tag characters", "app:tag:with:colons", false}, + {"Invalid image name with leading slash", "/app:latest", false}, + {"Invalid image name with trailing slash", "app/:latest", false}, + {"Invalid image name with consecutive slashes", "app//name:latest", false}, } for _, tt := range tests { diff --git a/internal/docker/function.go b/internal/docker/function.go index 3a38c81..b834e33 100644 --- a/internal/docker/function.go +++ b/internal/docker/function.go @@ -8,7 +8,6 @@ import ( "net/http" "os" "path/filepath" - "regexp" "strconv" "strings" "sync/atomic" @@ -16,21 +15,23 @@ import ( "403unlocker-cli/internal/common" + "github.com/distribution/reference" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/tarball" ) -// DockerImageValidator validates a Docker image name using a regular expression. +// DockerImageValidator validates a Docker image name using the same method as kubectl. +// This uses reference.ReferenceRegexp from github.com/distribution/reference package +// which provides comprehensive validation for Docker image references. func DockerImageValidator(imageName string) bool { - pattern := `^(?:[a-zA-Z0-9\-._]+(?::[0-9]+)?/)?` + - `(?:[a-z0-9\-._]+/)?` + - `[a-z0-9\-._]+` + - `(?::[a-zA-Z0-9\-._]+)?` + - `(?:@[a-zA-Z0-9\-._:]+)?$` - regex := regexp.MustCompile(pattern) - return regex.MatchString(imageName) && !strings.Contains(imageName, "@@") + if imageName == "" { + return false + } + + // Use the same validation method as kubectl + return reference.ReferenceRegexp.MatchString(imageName) } // customTransport tracks the number of bytes transferred during HTTP requests.