Proxy file server integrates with Google Drive for caching file with high frequency download
Requirement: Git, golang(version > 1.12), docker, openssl, windows or linux os
- Command
openssl genrsa -out private.pem 512
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
You can use 1024,... for length of key instead of 512, but can not small then 512 Long key make long token
- Output:
- private.pem is secret key use for your website to generate token or use for the proxy server generate token by api
- public.pem is key use for proxy for validating token purpose
- In your project folder :
git clone https://github.com/handl3r/proxy-fileserver
cd proxy-fileserver
# in windows
go build -o proxy-fileserver.exe cmd/main.go
# or in linux
go build -o proxy-fileserver cmd/main.go
- Fix .env file with your config:
- PROXY_SERVER_ENV= dev or prod, prod will come with log in json format
- SHARED_ROOT_FOLDER_LOCAL= parent folder of shared-folder on server
- SHARED_ROOT_FOLDER= shared-folder name your shared folder on Google Drive, same name with mirror folder on server
- SHARED_ROOT_FOLDER_ID= your shared folder id on Google Drive, example: 1eQ-6Ftg1G1ewA_-7puGpV_YCjHkpmkVe
- CACHE_TIME_LOCAL_FILE_SYSTEM= unit minute, time to delete a cached file on server from last download time
- AUTH_PUBLIC_KEY= path to your public key that generated by openssl, example certificates/public512.pem
- PRIVATE_KEY_LOCATION=certificates/private512.pem on proxy server
- EXPIRED_TIME_TOKEN=10M expired for token
- CYCLE_TIME_CLEANER= unit minute, cycle time cleaner wake up to clean expired files
- MYSQL_USER= username database
- MYSQL_PASSWORD= password database
- MYSQL_PORT=3306 port database mysql
- MYSQL_HOST= host database
- MYSQL_DATABASE= name database
- HTTP_PORT=8080 port http
- TOKEN_MODE token mode in 1, 2, 3
- GOOGLE_APPLICATION_CREDENTIALS= path to credential file (json) of service account cloud google
- CREDENTIAL_GOOGLE_OAUTH2_FILE=certificates/credentials.json google oauth drive credential
- TOKEN_GOOGLE_OAUTH2_FILE=certificates/token.json path to save token
- GOOGLE_OAUTH2_ENABLE=ON default is ON // if set to OFF, use must config service account
- INTERACTIVE_MODE=OFF default is off. Set to ON when use want to interact with terminal to exchange google access token
- TELEGRAM_BASE_URL= https://api.telegram.org base url for telegram api
- TELEGRAM_BOT_TOKEN= your token bot
- TELEGRAM_CHANNEL_ID= id of channel that bot will send message to
- .env and binary file must be in the same folder
- TOKEN_MODE:
- 1: no require token
- 2: token with no strict path: use medium_level_token
- 3: token strict path: use high_level_token
-
On Cloud Google:
- Create service account or create OAuth client ID if you want to use GOOGLE_OAUTH2_ENABLE and get certificate in json file (1)
- Enable Drive api
-
On Google Drive:
- Files storage in 'shared-folder'
- Share your 'shared-folder' with service account of google cloud if you use service account
- Get ID of 'shared-folder' on url
-
On server:
- Create parent folder: example 'temp'
- Create 'shared-folder' inside 'temp'
- Put your public key and certificate from (1) on somewhere: example 'certificates/cer.json', ' certificates/public512.pem'
- Define your .env file (see .env.example)
-
Use exg tool if you set INTERACTIVE_MODE=OFF to pre-generate token. exg-tool
Example structure of tree folder tree: example-tree-folder
On workspace:
./proxy-fileserver
- Request example:
http://localhost:8080/avt.jpg?token=dsahdha.dsad.ewuegud
- Response example:
- 204 for no file exist
- 500 for system error
- 200 for success response
package main
import (
"crypto/rsa"
"fmt"
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"log"
"time"
)
func GetCertificate(location string) (*rsa.PrivateKey, error) {
privateKeyBytes, err := ioutil.ReadFile(location)
if err != nil {
return nil, err
}
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes)
if err != nil {
return nil, err
}
return privateKey, nil
}
func main() {
privateKey, err := GetCertificate("private512.pem")
if err != nil {
panic(err)
}
t := jwt.New(jwt.GetSigningMethod("RS256"))
claims := jwt.MapClaims{}
claims["iat"] = time.Now().Unix()
claims["exp"] = time.Now().Add(1 * time.Minute).Unix()
t.Claims = claims
token, err := t.SignedString(privateKey)
if err != nil {
log.Printf("Error when generate token: %s", err)
}
fmt.Println(token)
}
- Request:
curl 'http://localhost:8080/shared-folder/avt.jpg?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTU0NzE2MTYsImlhdCI6MTYxNTQ3MTAxn0.jyX3RIaENdI6JTZdziN3c86cvpqj2M7hpFZTuCATMqtU8uzbs9tLjev21Gng9xwSikb5nY4BcCQRtx9ie29SwQ' \
-H 'Upgrade-Insecure-Requests: 1' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36' \
--compressed
- Response:
- Get file successfully:
- HTTP Code: 200
- Invalid token:
- HTTP Code: 401
- File not found:
- HTTP Code: 404
- System Error:
- HTTP Code: 500
- Body: "System error. Please contact admin!"
- Get file successfully:
- Medium level token:
- Request:
curl --location --request POST 'localhost:8080/auth' \ --header 'Content-Type: application/json' \ --data-raw '{ "type": "medium_level_token" }'
- Response:
{ "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTgxMzA4ODMsImlhdCI6MTYxODEyOTY4M30.Lyjr8-fIZdeUTueZHx47LwH0ynIEbgPp_gVYRZtdnikYt_zReFTIxLsOCMx1FZz9HSraE57TJc3D4avDZvbXuw" }
- Request:
- High level token:
- Request:
curl --location --request POST 'localhost:8080/auth' \ --header 'Content-Type: application/json' \ --data-raw '{ "type": "high_level_token", "path": "/avt.jpg" }'
- Response:
{ "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTgxMzEyMjQsImlhdCI6MTYxODEzMDAyNCwicGF0aCI6Ii9hdnQuanBnIn0.fAbwt13Hw8qXfYzAXU9ap_eAGngilQRgVwq63yN_PSRCYF3ye1Hv2YGirwhukc8MwPb6c3m0gxTkdvooZxWdxg" }
- Request:
-
Request:
curl --location --request POST 'localhost:8080/verify' \ --header 'Content-Type: application/json' \ --data-raw '{ "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTU0NzE2MTYsImlhdCI6MTYxNTQ3MTAxNn0.jyX3RIaENdI6JTZdziN3c86cvpqj2M7hpFZTuCATMqtU8uzbs9tLjev21Gng9xwSikb5nY4BcCQRtx9ie29SwQ" }'
-
Response:
- Valid:
- HTTP Code: 200
- Body: null
- Invalid:
- HTTP Code: 401
- Body: null
- Valid:
-
If any error log with Google Drive, re-share drive folder with service account and wait some minutes or maybe some hours. Google Drive need time to re-index your share-across-domain and longer with old files but immediately on new files.
-
Delete all records in database and all file in shared-folder before re-run
-
More information to config cloud google:
-
Confuse about INTERACTIVE_MODE:
- When interactive mode set to OFF, you must had access token and refresh token storage in TOKEN_GOOGLE_OAUTH2_FILE
- Encourage to use google_token_exchange tool to pre-generate token, then use INTERACTIVE_MODE=OFF in proxy server