rookie-ninja / rk-boot

Build microservice with rk-boot and let the team take over clean and tidy code.

Home Page:https://rkdev.info

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cannot override boot config values using environment variables

jeekishy opened this issue · comments

Description
Cannot override boot config values for JWT Middleware from environment variables.

Steps to reproduce the behavior:

  1. Create boot.yaml file
echo:
    - name: greeter
      port: 8080
      enabled: true
      middleware:
          jwt:
              enabled: false
              asymmetric:
                  algorithm: ""
                  publicKey: ""
                  privateKey: ""
  1. create a main.go file as per below and update environment variables for JWT token
package main

import (
	"context"
	"fmt"
	"github.com/labstack/echo/v4"
	rkboot "github.com/rookie-ninja/rk-boot/v2"
	rkecho "github.com/rookie-ninja/rk-echo/boot"
	"github.com/rookie-ninja/rk-echo/middleware/context"
	"net/http"
	"os"
)

func main() {
       // ideally this will be added by CI/CD pipeline I have added this as an example
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ENABLED", "true")
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_ALGORITHM", "ES256")
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_PUBLICKEY", "xxxxx")
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_PRIVATEKEY", "xxxxx")

	// Create a new boot instance.
	boot := rkboot.NewBoot()

	// Register handler
	echoEntry := rkecho.GetEchoEntry("greeter")
	echoEntry.Echo.GET("/v1/greeter", Greeter)

	// Bootstrap
	boot.Bootstrap(context.TODO())

	boot.WaitForShutdownSig(context.TODO())
}

func Greeter(ctx echo.Context) error {
	// Get JWT token
	rkechoctx.GetJwtToken(ctx)

	return ctx.JSON(http.StatusOK, &GreeterResponse{
		Message: fmt.Sprintf("Hello %s!", ctx.QueryParam("name")),
	})
}

type GreeterResponse struct {
	Message string
}
  1. Run program
go run main.go
test git:(HB-13764---Initial-VMS-Service-and-Auth) ✗ go run main.go
2022-09-21T12:09:50.429+1000	INFO	entry/util.go:299	Found ENV to override, applying...	{"env": ["RK_ECHO_0_MIDDLEWARE_JWT_ENABLED=true => echo[0].middleware.jwt.enabled=true", "RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_ALGORITHM=ES256 => echo[0].middleware.jwt.asymmetric.algorithm=ES256", "RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_PUBLICKEY=xxxxx => echo[0].middleware.jwt.asymmetric.publickey=xxxxx", "RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_PRIVATEKEY=xxxxx => echo[0].middleware.jwt.asymmetric.privatekey=xxxxx"]}
2022-09-21T12:09:50.430+1000	INFO	boot/echo_entry.go:682	Bootstrap EchoEntry	{"eventId": "28f73547-7079-4fa1-a6e5-9483e761b44b", "entryName": "greeter", "entryType": "EchoEntry"}
------------------------------------------------------------------------
endTime=2022-09-21T12:09:50.430602+10:00
startTime=2022-09-21T12:09:50.430542+10:00
elapsedNano=59241
timezone=AEST
ids={"eventId":"28f73547-7079-4fa1-a6e5-9483e761b44b"}
app={"appName":"rk","appVersion":"local","entryName":"greeter","entryType":"EchoEntry"}
env={"arch":"amd64","domain":"*","hostname":"C02YX1MSLVCF","localIP":"10.131.145.133","os":"darwin"}
payloads={"echoPort":8080}
counters={}
pairs={}
timing={}
remoteAddr=localhost
operation=Bootstrap
resCode=OK
eventStatus=Ended
EOE
  1. Make a request with Authorization in header, the request should have failed as the certs provided is not valid.
➜  auth git:(HB-13764---Initial-VMS-Service-and-Auth) ✗ curl -XGET -H 'Authorization: eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjo0NzA2MSwidmFsaWRhdGVkIjp0cnVlfSwiY29tbW9uIjp7ImNsaWVudCI6MTQsImJ1aWxkaW5nIjoxNTIsInRlbmFudCI6ODIyfSwic2NvcGVzIjpbInVzZXIuYXBwLmFsbCJdLCJpc3MiOiJsb2NhbGhvc3QiLCJleHAiOjIyNjQ4NTUxNDF9.ywC-EiD-GCWPM3cEor5i0Jcmwb3-ASf8sUjJ2SdQy55MCr8s8fFO1Xm3itWLFQczU2toEdUZv96VcteU0eTBHA' 'http://localhost:8080/v1/greeter'
{"Message":"Hello !"}

Expected behavior
The above request should not validate passed to the handler as I have use the env to overide the boot variables.

Additional context
The reason we require this is because when deploying our micro-services using CI/CD pipeline the configurations will get injected during build as environment variables.

@jeekishy Hi,I will check the the problem happens in your example. There may be bugs in it.

@jeekishy There was a bug in rk-entry which failed to recognize fields in boot.yaml while it was nested.
New release of rk-xxx family has been published with bug fixes.
Bug fix: rookie-ninja/rk-entry@ddaec60

In your case, please update bellow dependencies with newest version.

  • rk-boot: v2.2.6
  • rk-echo: v1.2.7

Reproduce

0: Bump up rk-boot and rk-echo dependencies

require (
	github.com/labstack/echo/v4 v4.7.2
	github.com/rookie-ninja/rk-boot/v2 v2.2.6
	github.com/rookie-ninja/rk-echo v1.2.7
)

1: Create ES256 key pair

openssl ecparam -name prime256v1 -genkey -noout -out private.ec.key
openssl ec -in private.ec.key -pubout -out public.pem

2: Create boot.yaml

echo:
  - name: greeter
    port: 8080
    enabled: true
    middleware:
      jwt:
        enabled: false
        asymmetric:
          algorithm: ""
          publicKey: ""
          privateKey: ""

3: Create main.go

package main

import (
	"context"
	"fmt"
	"github.com/labstack/echo/v4"
	"github.com/rookie-ninja/rk-boot/v2"
	"github.com/rookie-ninja/rk-echo/boot"
	"github.com/rookie-ninja/rk-echo/middleware/context"
	"net/http"
	"os"
)

func main() {
	// ideally this will be added by CI/CD pipeline I have added this as an example
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ENABLED", "true")
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_ALGORITHM", "ES256")
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_PUBLICKEY", "paste pub key generated before")
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_PRIVATEKEY", "paste private key generated before")

	// Create a new boot instance.
	boot := rkboot.NewBoot()

	// Register handler
	echoEntry := rkecho.GetEchoEntry("greeter")
	echoEntry.Echo.GET("/v1/greeter", Greeter)

	// Bootstrap
	boot.Bootstrap(context.TODO())

	boot.WaitForShutdownSig(context.TODO())
}

func Greeter(ctx echo.Context) error {
	// Get JWT token
	rkechoctx.GetJwtToken(ctx)

	return ctx.JSON(http.StatusOK, &GreeterResponse{
		Message: fmt.Sprintf("Hello %s!", ctx.QueryParam("name")),
	})
}

type GreeterResponse struct {
	Message string
}

4: Run program

go run main.go
2022-09-22T00:23:37.405+0800    INFO    entry/util.go:354       Found ENV to override, applying...      {"env": ["RK_ECHO_0_MIDDLEWARE_JWT_ENABLED=true => echo[0].middleware.jwt.enabled=true", "RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_ALGORITHM=ES256 => echo[0].middleware.jwt.asymmetric.algorithm=ES256", "RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_PUBLICKEY=xxxx => echo[0].middleware.jwt.asymmetric.publickey=xxxx", "RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_PRIVATEKEY=xxxx => echo[0].middleware.jwt.asymmetric.privatekey=xxxx"]}
2022-09-22T00:23:37.406+0800    INFO    boot/echo_entry.go:682  Bootstrap EchoEntry     {"eventId": "91843a4c-0e6a-4400-909e-bd17a0e5743a", "entryName": "greeter", "entryType": "EchoEntry"}
------------------------------------------------------------------------
endTime=2022-09-22T00:23:37.40694+08:00
startTime=2022-09-22T00:23:37.406892+08:00
elapsedNano=47378
timezone=CST
ids={"eventId":"91843a4c-0e6a-4400-909e-bd17a0e5743a"}
app={"appName":"rk","appVersion":"local","entryName":"greeter","entryType":"EchoEntry"}
env={"arch":"amd64","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin"}
payloads={"echoPort":8080}
counters={}
pairs={}
timing={}
remoteAddr=localhost
operation=Bootstrap
resCode=OK
eventStatus=Ended
EOE

5: Make request

curl -XGET -H 'Authorization: Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjo0NzA2MSwidmFsaWRhdGVkIjp0cnVlfSwiY29tbW9uIjp7ImNsaWVudCI6MTQsImJ1aWxkaW5nIjoxNTIsInRlbmFudCI6ODIyfSwic2NvcGVzIjpbInVzZXIuYXBwLmFsbCJdLCJpc3MiOiJsb2NhbGhvc3QiLCJleHAiOjIyNjQ4NTUxNDF9.ywC-EiD-GCWPM3cEor5i0Jcmwb3-ASf8sUjJ2SdQy55MCr8s8fFO1Xm3itWLFQczU2toEdUZv96VcteU0eTBHB' 'http://localhost:8080/v1/greeter'
{"error":{"code":401,"status":"Unauthorized","message":"Invalid or expired jwt","details":[]}}

Prevent print private keys in log

In order to prevent program print overridden keys in log, we can override pub key and private key with path.

boot.yaml

echo:
  - name: greeter
    port: 8080
    enabled: true
    middleware:
      jwt:
        enabled: false
        asymmetric:
          algorithm: ""
          publicKeyPath: ""
          privateKeyPath: ""

main.go

package main

import (
	"context"
	"fmt"
	"github.com/labstack/echo/v4"
	"github.com/rookie-ninja/rk-boot/v2"
	"github.com/rookie-ninja/rk-echo/boot"
	"github.com/rookie-ninja/rk-echo/middleware/context"
	"net/http"
	"os"
)

func main() {
	// ideally this will be added by CI/CD pipeline I have added this as an example
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ENABLED", "true")
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_ALGORITHM", "ES256")
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_PUBLICKEYPATH", "public.pem")
	_ = os.Setenv("RK_ECHO_0_MIDDLEWARE_JWT_ASYMMETRIC_PRIVATEKEYPATH", "private.ec.key")

	// Create a new boot instance.
	boot := rkboot.NewBoot()

	// Register handler
	echoEntry := rkecho.GetEchoEntry("greeter")
	echoEntry.Echo.GET("/v1/greeter", Greeter)

	// Bootstrap
	boot.Bootstrap(context.TODO())

	boot.WaitForShutdownSig(context.TODO())
}

func Greeter(ctx echo.Context) error {
	// Get JWT token
	rkechoctx.GetJwtToken(ctx)

	return ctx.JSON(http.StatusOK, &GreeterResponse{
		Message: fmt.Sprintf("Hello %s!", ctx.QueryParam("name")),
	})
}

type GreeterResponse struct {
	Message string
}

@dongxuny thank you for the fast turn around. It is working as expected now. :)