diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5bd7105 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Ignore env file +.env + +# Ignore pem files +*.pem + + diff --git a/Makefile b/Makefile index b4af9ac..3e8eb92 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,37 @@ -make: build +make: build-infra build -FGA_API_HOST="http://localhost:8080" +# Default environment variables +FGA_API=$${FGA_API:-"http://localhost:8080"} +PRIV_KEY_FILE=$${PRIV_KEY_FILE:-"jws-priv-key.pem"} +PRIV_KEY_FILE_SIZE=$${PRIV_KEY_FILE_SIZE:-2048} +DOCKER_FILE=$${DOCKER_FILE:-"docker-compose.yaml"} -.PHONY: infra-up +# Phony targets +.PHONY: infra-down infra-up check build clean + + +# Generate private key +gen-key: + echo ${PRIV_KEY_FILE} ${PRIV_KEY_FILE_SIZE} + @openssl genrsa -out ${PRIV_KEY_FILE} ${PRIV_KEY_FILE_SIZE} infra-up: - @docker compose -f docker-compose.yaml up -d + @docker compose -f ${DOCKER_FILE} up -d + +infra-down: + @docker compose -f ${DOCKER_FILE} down + -.PHONY: check check: - @curl -X GET "${FGA_API_HOST}/healthz" + @curl -X GET "${FGA_API}/healthz" -.PHONY: build build: @go build +run: + @env $(cat .env | grep -v "#" | xargs) ./fga-demo + +# Clean binary data and priv key clean: @go clean + @rm ${PRIV_KEY_FILE} diff --git a/fga-demo b/fga-demo deleted file mode 100755 index 5e16a5e..0000000 Binary files a/fga-demo and /dev/null differ diff --git a/gen_jwt.sh b/gen_jwt.sh new file mode 100755 index 0000000..d068a87 --- /dev/null +++ b/gen_jwt.sh @@ -0,0 +1,36 @@ +# From https://stackoverflow.com/questions/59002949/how-to-create-a-json-web-token-jwt-using-openssl-shell-commands +key=jws-priv-key.pem + +# Construct the header +jwt_header=$(echo -n '{"alg":"HS256","typ":"JWT"}' | base64 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//) + +# ans: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 + +# Construct the payload +payload=$(echo -n '{"email":"jordan@example.com"}' | base64 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$//) + +# ans: eyJlbWFpbCI6ImpvcmRhbkBleGFtcGxlLmNvbSJ9 + +# Store the raw user secret (with example of newline at end) +secret=$'bigsecretisveryhardtoguessbysneakypeopleright\n' + +# Note, because the secret may have newline, need to reference using form $"" +echo -n "$secret" + +# Convert secret to hex (not base64) +hexsecret=$(echo -n "$secret" | xxd -p | paste -sd "") + +# ans: 62696773656372657469737665727968617264746f67756573736279736e65616b7970656f706c6572696768740a + +# For debug, also display secret in base64 (for input into https://jwt.io/) +echo -n "$secret" | base64 + +# ans: Ymlnc2VjcmV0aXN2ZXJ5aGFyZHRvZ3Vlc3NieXNuZWFreXBlb3BsZXJpZ2h0Cg== + +# Calculate hmac signature -- note option to pass in the key as hex bytes +hmac_signature=$(echo -n "${jwt_header}.${payload}" | openssl dgst -sha256 -mac HMAC -macopt hexkey:$hexsecret -binary | base64 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//) + +# Create the full token +jwt="${jwt_header}.${payload}.${hmac_signature}" + +# ans: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvcmRhbkBleGFtcGxlLmNvbSJ9.C3MVjfmnul8dLNIgiv6Dt3jSefD07Y0QtDrOZ5oYSXo diff --git a/main.go b/main.go index 81d04ef..0a1eaf6 100644 --- a/main.go +++ b/main.go @@ -2,11 +2,13 @@ package main import ( "context" - "crypto/rand" "crypto/rsa" + "errors" "fmt" + "io/ioutil" "log" "os" + "strconv" "github.com/gofiber/fiber/v2" jwt_fiber "github.com/gofiber/jwt/v3" @@ -14,38 +16,12 @@ import ( fga "github.com/openfga/go-sdk" ) -const privKeyPEM = ` ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAtezzFs7wK1WqA7lFUOqbCNXkcnHKGs/eIlHHN1MfXN6QjzBU -1yz+d+QeRM986PYttFB041RF+9QoNJcfow26TUhwgY8UEyeh+93hYGWT/grWJn3r -lKhZcjOBQ3M9DDsg2oEoRZI7pOwUcoj0OpJHz0dcoj4zrkfY1JPCWLFJbr1QszsX -Yy/soga2M5jHyVNtYz/c4JAGjQMZpnA7tGbdsGFvnZDdH1t5naKX+ybAQYItwWjo -9kBwhf1QGoaEVm0gvu9e+VaqKT+qnqMsGqYW29iKWOSerIg1lushZL8TulAPMwFs -wzttRvEPqcQzCKYtbNfenmvZJIajA6HYq/lc7QIDAQABAoIBAE201Sxj3dAUuhb3 -FvV2EByZCAgeNH55VV+BYL9v4NCRPFv8//AdBuB87rTjj24OYP1I9HR5dZ8YQsgb -2OaToYULMQsV6zQ3VIg5gN/k4266gDhWxr5rnjEacNc3rNbBlsneKy50RMewExfN -CczO2J9f0uB2AyspAyPhrTynFT+YIvE8YflbLhUVVNhsSY/yoNTmd/ybNn5tTDQY -uT1v4wVw/6QGW1FTAmbR27MN1D+ALfFKyG8xJ5ElfoVWciiy/rneo+Ty1EZF/Z32 -DFCt5/pNr9g0hPIlBnCtMOHRUmTbfnHuWiJ0ibaMr6UC+AVgiIUKxpjbQT3hDAkW -MX9VTB0CgYEA5Az5/hGYBWkjgsk0atwK5hTyKmnxq/fIvXVReh6PT+dIeEbBkWud -kTat4eMZMJA/gGwkxwyBNXodFWYIhWxjiGdIpS2SXiyfQRTZPi1o3VOuw4OiYGBf -pb6kwD+dT39QcoOAU+PsQJesONbc4EETrVnPSE3+QW3YBsb2+my2KSsCgYEAzDjP -tyqR06rgwCcHF4EDIMizCRXhIXmMKrQyueI2QbtFFzpeTei1HzE5zqia/PlPpmWQ -t2ZadBBIhgj+XJF70GTq4v1Mphx8YaMkZlfjRree0vOLTXHmlV8IL/i2SnYTvzRg -PPFIUGxN9ICqNMC+syBdTUsnF4GxRbartEQe1kcCgYA3skukF5vvRlxb5tQcfR1U -UC1M2o8hluS6ENsTXj9WHoB+j5la8NOM0bPqBVLzn2dC8CaTTqSH8QkFXuQZ4fz1 -JXSCkvy6b199v5/HJcQEuhlmutF/hEoX6tXF9kNvVQLK1P8UfggHRoPTZfaP9pTd -s5+CYzoDtNYb4aAUTVS3/QKBgF/uG9x9maylKxf3/UPULWT8AeW1qmAwGWCB7wYU -Ncpgro6/s0bDljkFxZLG7q8AaaLPONB9uuWkNH0Jwno3OMLmdNOViqjI3sB6gwG0 -LSWt4WRUVM5XP6pQzqbCHNcTaik58C8QZBirF19QBSPsmmfIPyusrrtDB8OokDWI -fjfHAoGAJEwPDCzGi/4UXd221GaYidif2GW8Dpo2kqZQAC42IQwxuEw3LgWMj0v4 -IQEfT6OOYgeMmM5/qJ0RSkox6uvwlUzNpcaFzXAfmu+JNxo2LFvV/dkS+BbZyZ/Z -HfmMlyl+W5OXOHodg+R7J/UWupYnPp0TwlWKWYUCfM88KbIl2jk= ------END RSA PRIVATE KEY----- -` +const PRIV_KEY_PATH_ENV = "PRIV_KEY_PATH" +const PRIV_KEY_SIZE_ENV = "PRIV_KEY_SIZE" var ( - privateKey *rsa.PrivateKey + privateKey *rsa.PrivateKey + privateKeySize int ) func createStore() { @@ -82,40 +58,65 @@ func createStore() { fmt.Println(store_resp) fmt.Println(http_resp) - } -const PRIV_KEY_SIZE = 2048 +func readRSAPrivKey(path string) (*rsa.PrivateKey, error) { + file, errOpen := os.Open(os.Getenv(PRIV_KEY_PATH_ENV)) -func readController(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - name := claims["name"].(string) - return c.SendString(name + " read " + c.Params("document")) -} - -func runFiber() { - app := fiber.New() - rng := rand.Reader - var err error - privateKey, err = rsa.GenerateKey(rng, PRIV_KEY_SIZE) - - if err != nil { - log.Fatalf("rsa.GenerateKey: %v", err) - os.Exit(1) + if errOpen != nil { + log.Fatalf("Error while loading %s private key", PRIV_KEY_PATH_ENV) + return nil, errors.New("Error while opening private key") } + defer file.Close() + + keyData, errReading := ioutil.ReadAll(file) + + if errReading != nil { + log.Fatalf("Error reading %s private key", PRIV_KEY_PATH_ENV) + return nil, errors.New("Error while reading private key") + } + + return jwt.ParseRSAPrivateKeyFromPEM(keyData) +} + +func readDocument(c *fiber.Ctx) error { + log.Println(c) + // user := c.Locals("user").(*jwt.Token) + // claims := user.Claims.(jwt.MapClaims) + // name := claims["name"].(string) + // return c.SendString(name + " read " + c.Params("document")) + return nil +} + +func runFiber(privateKey *rsa.PrivateKey) { + app := fiber.New() + + log.Println(privateKey.Public()) app.Use(jwt_fiber.New(jwt_fiber.Config{ SigningMethod: "RS256", SigningKey: privateKey.Public(), })) - app.Get("/read/:document", readController) + app.Get("/read/:document", readDocument) app.Listen(":9999") } func main() { + var err error = nil + privateKeySize, err = strconv.Atoi(os.Getenv(PRIV_KEY_SIZE_ENV)) + + if err != nil { + log.Fatalf("Error while geting private key size from %s environment variable", PRIV_KEY_SIZE_ENV) + } + + privateKey, err = readRSAPrivKey(os.Getenv(PRIV_KEY_PATH_ENV)) + + if err != nil { + log.Fatalf("Error while geting private key from %s environment variable", PRIV_KEY_PATH_ENV) + } + createStore() - runFiber() + runFiber(privateKey) }