diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9ef93c2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM siteworxpro/golang:latest AS build + +WORKDIR /app + +ADD . . + +ENV GOPRIVATE=git.s.int +ENV GOPROXY=direct +ENV CGO_ENABLED=0 + +RUN go mod tidy && go build -o imgproxy . + +FROM alpine AS runtime + +EXPOSE 8080 + +WORKDIR /app + +COPY --from=build /app/imgproxy /app/imgproxy + +ENTRYPOINT ["/app/imgproxy", "server"] \ No newline at end of file diff --git a/README.md b/README.md index 07e05a9..3b33f05 100644 --- a/README.md +++ b/README.md @@ -106,3 +106,31 @@ generate a url with a specified format https://i.fooo.com/UMkz4OUNw6P9ShLdewuvW3ValMgCt263vZzU5gN57WQ/h:200/sm:1/enc/ECYxMeVBTjRxB7F-jdQ7W_-Fnv4YbmSJIKie-Hdtxd9vsmEKjU1YuWVSzdN97Mod.bmp ``` +## decryption + +if you need to decrypt a url you have already created just copy the encrypted portion of the url + +```shell +./imgproxy decrypt -u ECYxMeVBTjRxB7F-jdQ7W_-Fnv4YbmSJIKie-Hdtxd9vsmEKjU1YuWVSzdN97Mod +``` + +## web service +you can also serve request via a web request + +```shell +./imgproxy server +``` + +```shell +curl --location 'http://localhost:8080/generate' \ +--header 'Content-Type: application/json' \ +--data '{ + "image": "s3://my.image.bucket/C81A0923.jpg", + "params": [ + "q:40", + "w:400" + ], + "format": "bmp" +}' +``` +`https://i.fooo.com/UMkz4OUNw6P9ShLdewuvW3ValMgCt263vZzU5gN57WQ/h:200/sm:1/enc/ECYxMeVBTjRxB7F-jdQ7W_-Fnv4YbmSJIKie-Hdtxd9vsmEKjU1YuWVSzdN97Mod.bmp` \ No newline at end of file diff --git a/build.sh b/build.sh index 3528a2d..bba0859 100755 --- a/build.sh +++ b/build.sh @@ -8,7 +8,7 @@ do if [[ ${arrIN[0]} == 'linux' || ${arrIN[0]} == 'darwin' || ${arrIN[0]} == 'freebsd' || ${arrIN[0]} == 'windows' ]]; then echo "Building $distro..." - GOOS=${arrIN[0]} GOARCH=${arrIN[1]} go build --ldflags="-X 'main.Version=$(git describe --tags --abbrev=0)'" -o "dist/img-proxy-url-generator_${arrIN[0]}_${arrIN[1]}" + GOOS=${arrIN[0]} GOARCH=${arrIN[1]} GO111MODULE=on CGO_ENABLED=0 go build --ldflags="-X 'main.Version=$(git describe --tags --abbrev=0)'" -o "dist/img-proxy-url-generator_${arrIN[0]}_${arrIN[1]}" gpg --detach-sign "dist/img-proxy-url-generator_${arrIN[0]}_${arrIN[1]}" fi done \ No newline at end of file diff --git a/generator/crypt.go b/generator/crypt.go index ffe8744..f1927c3 100644 --- a/generator/crypt.go +++ b/generator/crypt.go @@ -5,6 +5,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" + "encoding/base64" "io" ) @@ -14,6 +15,26 @@ func pkcs7pad(data []byte, blockSize int) []byte { return append(data, padding...) } +func (g *Generator) Decrypt(s string) (string, error) { + c, err := aes.NewCipher(g.config.encryptionKeyBin) + if err != nil { + return "", err + } + + decoded, err := base64.RawURLEncoding.DecodeString(s) + if err != nil { + return "", err + } + + iv := decoded[:aes.BlockSize] + cryptText := decoded[aes.BlockSize:] + cbc := cipher.NewCBCDecrypter(c, iv) + + cbc.CryptBlocks(cryptText, cryptText) + + return string(cryptText), err +} + func (g *Generator) generateBaseAesEncUrl(file []byte) (string, error) { c, err := aes.NewCipher(g.config.encryptionKeyBin) if err != nil { diff --git a/go.sum b/go.sum index 11a73a2..5deb731 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,9 @@ -github.com/aws/aws-sdk-go v1.54.18 h1:t8DGtN8A2wEiazoJxeDbfPsbxCKtjoRLuO7jBSgJzo4= -github.com/aws/aws-sdk-go v1.54.18/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go v1.54.19 h1:tyWV+07jagrNiCcGRzRhdtVjQs7Vy41NwsuOcl0IbVI= github.com/aws/aws-sdk-go v1.54.19/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/bigkevmcd/go-configparser v0.0.0-20230427073640-c6b631f70126 h1:uru++pUKoS/yYU3Ohq9VItZdK/cT7FFJH/UUjOlxc+s= -github.com/bigkevmcd/go-configparser v0.0.0-20230427073640-c6b631f70126/go.mod h1:zqqfbfnDeSdRs1WihmMjSbhb2Ptw8Jbus831xoqiIec= github.com/bigkevmcd/go-configparser v0.0.0-20240624060122-ccd05f93a9d2 h1:2qnRmzKO1fVs2UOqyt6tRMpVB8FrOnx8IG8C2lK0cjU= github.com/bigkevmcd/go-configparser v0.0.0-20240624060122-ccd05f93a9d2/go.mod h1:vzEQfW+A1T+AMJmTIX+SXNLNECHOM7GEinHhw0IjykI= -github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= -github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs= github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8= github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= @@ -30,16 +24,12 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -48,13 +38,9 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/main.go b/main.go index 40efa64..3b9aa6d 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "github.com/bigkevmcd/go-configparser" "github.com/siteworxpro/img-proxy-url-generator/aws" @@ -11,6 +12,7 @@ import ( "log" "net/http" "os" + "strings" ) var keyBin, saltBin []byte @@ -21,6 +23,12 @@ var Version = "v0.0.0" var awsConfig aws.Config +type jsonRequest struct { + Image string `json:"image"` + Params []string `json:"params"` + Format string `json:"format"` +} + func main() { pr := printer.NewPrinter() @@ -52,16 +60,40 @@ func main() { }, }) - _, err := os.Stat("./templates") - if !os.IsNotExist(err) { - commands = append(commands, &cli.Command{ - Name: "server", - Usage: "Start a webserver for s3 file browsing", - Action: func(c *cli.Context) error { - return startServer(c, pr) + commands = append(commands, &cli.Command{ + Name: "server", + Usage: "Start a webserver for s3 file browsing", + Action: func(c *cli.Context) error { + return startServer(c, pr) + }, + }) + + commands = append(commands, &cli.Command{ + Name: "decrypt", + Usage: "decrypt an image url contents", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "url", + Aliases: []string{"u"}, + Required: true, }, - }) - } + }, + Action: func(c *cli.Context) error { + err := initGenerator(c.String("config")) + if err != nil { + return err + } + + plain, err := imgGenerator.Decrypt(c.String("url")) + if err != nil { + return err + } + + pr.LogSuccess(plain) + + return nil + }, + }) app := &cli.App{ Name: "img-proxy-url-generator", @@ -82,7 +114,7 @@ func main() { }, } - err = app.Run(os.Args) + err := app.Run(os.Args) if err != nil { pr.LogError(err.Error()) @@ -96,41 +128,73 @@ func startServer(c *cli.Context, p *printer.Printer) error { return err } - awsClient := aws.NewClient(&awsConfig) + _, err = os.Stat("./templates") + if !os.IsNotExist(err) { + awsClient := aws.NewClient(&awsConfig) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + contToken := r.URL.Query().Get("next") - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - contToken := r.URL.Query().Get("next") + var next *string + if contToken == "" { + next = nil + } else { + next = &contToken + } - var next *string - if contToken == "" { - next = nil - } else { - next = &contToken - } + contents, err := awsClient.ListBucketContents(next) + if err != nil { + return + } + + for i, content := range contents.Images { + contents.Images[i].Url, _ = signURL("s3://"+awsConfig.Bucket+"/"+content.Name, []string{"pr:sq"}, "") + contents.Images[i].Download, _ = signURL("s3://"+awsConfig.Bucket+"/"+content.Name, []string{""}, "") + } + + file, _ := os.ReadFile("./templates/index.gohtml") + + tmpl := template.Must(template.New("index").Parse(string(file))) + + err = tmpl.Execute(w, contents) + + if err != nil { + println(err.Error()) + } + }) + } + + http.HandleFunc("/generate", func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + w.WriteHeader(404) - contents, err := awsClient.ListBucketContents(next) - if err != nil { return } - for i, content := range contents.Images { - contents.Images[i].Url, _ = signURL("s3://"+awsConfig.Bucket+"/"+content.Name, []string{"pr:sq"}, "") - contents.Images[i].Download, _ = signURL("s3://"+awsConfig.Bucket+"/"+content.Name, []string{""}, "") - } - - file, _ := os.ReadFile("./templates/index.gohtml") - - tmpl := template.Must(template.New("index").Parse(string(file))) - - err = tmpl.Execute(w, contents) + bodyContents := make([]byte, r.ContentLength) + _, _ = r.Body.Read(bodyContents) + jr := jsonRequest{} + err = json.Unmarshal(bodyContents, &jr) if err != nil { println(err.Error()) + w.WriteHeader(500) + return } + + url, err := signURL(jr.Image, jr.Params, jr.Format) + if err != nil { + println(err.Error()) + w.WriteHeader(500) + return + } + + log.Println(fmt.Sprintf("%s - [%s] - (%s)", jr.Image, strings.Join(jr.Params, ","), url)) + + _, _ = w.Write([]byte(url)) }) p.LogSuccess("Starting http server on port 8080. http://localhost:8080") - log.Fatal(http.ListenAndServe(":8080", nil)) + log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil)) return nil }