Skip to content

Commit

Permalink
Add two new endpoints and other generic fixes/changes (#16)
Browse files Browse the repository at this point in the history
Changes:
* Fixes correct marshaling of AuthSignRequest before it's sent to BankID
  The twofer API expect UserVisibleData and UserNonVisibleData to be sent in
  clear-text, but it must be sent to BankID as Base64 encoded data. However
  The fields were previously only Base64 encoded when validating the max length
  but is now also Base64 encoded before the fields are sent to BankID.
* Switch docker image to be built from scratch
* Update GO and dependency versions to resolve a number of CVE's
* Increase graceful shutdown period
  A BankID auth order is only valid for 30 seconds, unless the QR-code is
  scanned, then the order is valid for 180 seconds.

Fixes:
* Fixes 'superfluous response.WriteHeader' console message
* Refactor BankID loops waiting for status changes
* Exit process if we're unable to start the webserver
* Avoid error message in tests when server have been gracefully shut down
* Avoid error message when server have been gracefully shut down
* Simplify shutdown process

New:
* Add prestophook command
  Add a new `twoferd prestophook` command that can be used in a K8S manifest to
  handle graceful shutdown scenarios, where the service may need more than 30
  seconds to shutdown.
* Add two new (v2) auth and sign endpoints that have a slightly different API
* Add public sse package
* Add public NDJSON package
  Add NDJSON as a new default stream encoder for v2 endpoints
  Set STREAM_ENCODER environment variable to "SSE", to use SSE as stream encoder.
  • Loading branch information
larsve authored Apr 25, 2024
1 parent 90a4d21 commit f103f17
Show file tree
Hide file tree
Showing 26 changed files with 1,697 additions and 163 deletions.
42 changes: 42 additions & 0 deletions api/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package api

type (
BankIdV6Response struct {
OrderRef string `json:"orderRef"`
ErrorCode string `json:"errorCode,omitempty"`
ErrorText string `json:"errorText,omitempty"`
URI string `json:"uri,omitempty"`
QR string `json:"qr,omitempty"`
Status string `json:"status,omitempty"`
HintCode string `json:"hintCode,omitempty"`
CompletionData *BankIdV6CompletionData `json:"completionData,omitempty"`
}
BankIdV6CompletionData struct {
User BankIdV6User `json:"user,omitempty"`
Device BankIdV6Device `json:"device,omitempty"`
BankIdIssueDate string `json:"bankIdIssueDate,omitempty"`
StepUp BankIdV6StepUp `json:"stepUp,omitempty"`
Signature string `json:"signature,omitempty"`
OcspResponse string `json:"ocspResponse,omitempty"`
}
BankIdV6User struct {
PersonalNumber string `json:"personalNumber,omitempty"`
Name string `json:"name,omitempty"`
GivenName string `json:"givenName,omitempty"`
SurName string `json:"surName,omitempty"`
}
BankIdV6Device struct {
IpAddress string `json:"ipAddress,omitempty"`
UHI string `json:"uhi,omitempty"`
}
BankIdV6StepUp struct {
MRTD bool `json:"mrtd,omitempty"`
}
)

const (
StatusPending = "pending"
StatusComplete = "complete"
StatusFailed = "failed"
StatusError = "error"
)
13 changes: 7 additions & 6 deletions cmd/twoferd/Dockerfile.build
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM golang:1.20.3-alpine3.17 as builder
FROM golang:1.21.9-alpine3.19 as builder

RUN apk add --no-cache git curl build-base bash shadow
#RUN apk add --no-cache git curl build-base bash shadow

RUN mkdir -p /go/src/twofer
WORKDIR /go/src/twofer
Expand All @@ -9,10 +9,11 @@ COPY . /go/src/twofer

RUN ls

RUN go build -o /twoferd ./cmd/twoferd/main.go
RUN CGO_ENABLE=0 go build -o /twoferd ./cmd/twoferd/main.go

FROM alpine:3.17.3
RUN apk add --no-cache tzdata ca-certificates
#FROM alpine:3.19.1
#RUN apk add --no-cache tzdata ca-certificates
FROM scratch
EXPOSE 8080
COPY --from=builder /twoferd /
CMD /twoferd
CMD [ "/twoferd" ]
2 changes: 1 addition & 1 deletion cmd/twoferd/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.20.3
FROM golang:1.21.9

RUN apt-get update && apt-get install -y inotify-hookable

Expand Down
93 changes: 69 additions & 24 deletions cmd/twoferd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,37 @@ package main

import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"

"github.com/modfin/twofer/internal/config"
"github.com/modfin/twofer/internal/eid/bankid"
"github.com/modfin/twofer/internal/httpserve"
"github.com/modfin/twofer/internal/serveid"
"github.com/modfin/twofer/internal/servotp"
"github.com/modfin/twofer/internal/servpwd"
"github.com/modfin/twofer/internal/servqr"
"log"
"os"
"os/signal"
"sync"
"syscall"
"time"

"github.com/labstack/echo/v4"
"github.com/modfin/twofer/stream/ndjson"
"github.com/modfin/twofer/stream/sse"
)

const shutdownGracePeriod = time.Second * 200 // A BankID auth order is only valid for 30 seconds, unless it's scanned, then it's valid for 180 seconds.

func main() {
if len(os.Args) > 1 && os.Args[1] == "prestophook" {
prestophook()
return
}

cfg := config.Get()
e := echo.New()
e.Use(middleware.Logger())
Expand Down Expand Up @@ -89,28 +100,36 @@ func main() {
}

func startServer(e *echo.Echo) {
appCtx, appClose := context.WithCancel(context.Background())
go func() {
fmt.Println(e.Start(":8080"))
err := e.Start(":8080")
if !errors.Is(err, http.ErrServerClosed) {
fmt.Println(err)
}
appClose()
}()

signalChannel := make(chan os.Signal, 1)
signal.Notify(signalChannel, syscall.SIGTERM)
signal.Notify(signalChannel, syscall.SIGTERM, syscall.SIGINT)

<-signalChannel
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
timeout, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
select {
case s := <-signalChannel:
fmt.Printf("twoferd received signal: %v\n", s)
appClose() // Cancel 'app context' when we receive SIGTERM
case <-appCtx.Done():
}

err := e.Shutdown(timeout)
if err != nil {
log.Fatalf("failure during Echo's shutdown: %v", err)
}
wg.Done()
}()
fmt.Println("Graceful shutdown initiated...")

timeout, cancel := context.WithTimeout(context.Background(), shutdownGracePeriod)
defer cancel()

wg.Wait()
err := e.Shutdown(timeout)
if err != nil {
log.Fatalf("failure during Echo's shutdown: %v", err)
}

fmt.Println("twoferd stopped")
}

func startEid(e *echo.Echo) {
Expand Down Expand Up @@ -149,6 +168,32 @@ func startEid(e *echo.Echo) {
} else {
fmt.Println(" - Adding BankId v6.0")
fmt.Println(" - BankId Client Cert NotAfter:", bankid.ParsedClientCert().NotAfter)
httpserve.RegisterBankIDServer(e, bankid.APIv60)
httpserve.RegisterBankIDServer(e, bankid.APIv60, getStreamEncoder(config.Get().StreamEncoder))
}
}

func getStreamEncoder(encoder string) httpserve.NewStreamEncoder {
switch encoder {
case "SSE":
return sse.NewEncoder
default:
return ndjson.NewEncoder
}
}

func prestophook() {
fmt.Println("twofer - prestophook")

// Give K8S time to remove POD from service before stutdown is started
time.Sleep(time.Second)

// TODO: Check that twofer is actually PID 1 before we try to send signal?
err := syscall.Kill(1, syscall.SIGINT)
if err != nil {
fmt.Printf("prestophook: SIGINT error: %v", err)
return
}

// Wait for graceful shutdown period to end. When PID 1 have exited, we'll be terminated as well
time.Sleep(shutdownGracePeriod)
}
37 changes: 12 additions & 25 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,52 +1,39 @@
module github.com/modfin/twofer

go 1.19
go 1.21

require (
github.com/caarlos0/env/v6 v6.10.1
github.com/davecgh/go-spew v1.1.1
github.com/go-webauthn/webauthn v0.8.2
github.com/golang/protobuf v1.5.3
github.com/gorilla/mux v1.8.0
github.com/labstack/echo/v4 v4.10.2
github.com/mdp/qrterminal/v3 v3.0.0
github.com/google/uuid v1.3.0
github.com/labstack/echo/v4 v4.12.0
github.com/pquerna/otp v1.4.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.2
github.com/urfave/cli/v2 v2.20.2
golang.org/x/crypto v0.8.0
golang.org/x/net v0.9.0
golang.org/x/oauth2 v0.7.0
google.golang.org/grpc v1.52.0-dev
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.22.0
golang.org/x/net v0.24.0
)

require (
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/go-webauthn/revoke v0.1.9 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-tpm v0.3.3 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect
google.golang.org/protobuf v1.30.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/qr v0.2.0 // indirect
)
Loading

0 comments on commit f103f17

Please sign in to comment.