Update SCS session manager

This commit is contained in:
paul 2019-05-15 19:08:24 +02:00
parent f9f6fb64b9
commit 8aebe43fd3
6 changed files with 36 additions and 59 deletions

View file

@ -19,8 +19,7 @@ ENV \
VPN_DEV="tun" \ VPN_DEV="tun" \
VPN_HOST="vpn.example.com" \ VPN_HOST="vpn.example.com" \
VPN_PORT="1194" \ VPN_PORT="1194" \
VPN_PROTO="udp" \ VPN_PROTO="udp"
APP_KEY=""
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=0 /go/src/github.com/zom-bi/ovpn-certman/certman / COPY --from=0 /go/src/github.com/zom-bi/ovpn-certman/ovpn-certman /certman
ENTRYPOINT ["/certman"] ENTRYPOINT ["/certman"]

View file

@ -38,7 +38,6 @@ variables:
* `OAUTH2_TOKEN_URL` the URL to the "/token" endpoint of the identity provider * `OAUTH2_TOKEN_URL` the URL to the "/token" endpoint of the identity provider
* `OAUTH2_REDIRECT_URL` the redirect URL used by the app, usually the hostname suffixed by "/login/oauth2/redirect" * `OAUTH2_REDIRECT_URL` the redirect URL used by the app, usually the hostname suffixed by "/login/oauth2/redirect"
* `USER_ENDPOINT` the URL to the Identity provider user endpoint, for gitlab this is "/api/v4/user". The "username" attribute of the returned JSON will used for authentication. * `USER_ENDPOINT` the URL to the Identity provider user endpoint, for gitlab this is "/api/v4/user". The "username" attribute of the returned JSON will used for authentication.
* `APP_KEY` random ASCII string, 32 characters in length. Used for cookie generation.
* `APP_LISTEN` port and ip to listen on, e.g. `:8000` or `127.0.0.1:3000` * `APP_LISTEN` port and ip to listen on, e.g. `:8000` or `127.0.0.1:3000`
* `VPN_DEV` which device is used by the network, either `tun` or `tap` (check server cfg) * `VPN_DEV` which device is used by the network, either `tun` or `tap` (check server cfg)
* `VPN_HOST` Hostname or IP address of the server * `VPN_HOST` Hostname or IP address of the server

View file

@ -22,10 +22,8 @@ func main() {
c := services.Config{ c := services.Config{
CollectionPath: "./clients.json", CollectionPath: "./clients.json",
Sessions: &services.SessionsConfig{ Sessions: &services.SessionsConfig{
SessionName: "_session", HTTPOnly: true,
CookieKey: os.Getenv("APP_KEY"), Lifetime: 24 * time.Hour,
HttpOnly: true,
Lifetime: 24 * time.Hour,
}, },
} }

View file

@ -13,6 +13,7 @@ func RequireLogin(sessions *services.Sessions) func(http.Handler) http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) { fn := func(w http.ResponseWriter, req *http.Request) {
if username := sessions.GetUsername(req); username == "" { if username := sessions.GetUsername(req); username == "" {
http.Redirect(w, req, "/login", http.StatusFound) http.Redirect(w, req, "/login", http.StatusFound)
return
} }
next.ServeHTTP(w, req) next.ServeHTTP(w, req)

View file

@ -5,25 +5,24 @@ import (
"os" "os"
"strings" "strings"
"github.com/zom-bi/ovpn-certman/services" "github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/gorilla/csrf"
"github.com/gorilla/securecookie"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"github.com/zom-bi/ovpn-certman/assets" "github.com/zom-bi/ovpn-certman/assets"
"github.com/zom-bi/ovpn-certman/handlers" "github.com/zom-bi/ovpn-certman/handlers"
"github.com/zom-bi/ovpn-certman/views"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/gorilla/csrf"
mw "github.com/zom-bi/ovpn-certman/middleware" mw "github.com/zom-bi/ovpn-certman/middleware"
"github.com/zom-bi/ovpn-certman/services"
"github.com/zom-bi/ovpn-certman/views"
) )
var ( var (
// TODO: make this configurable // TODO: make this configurable
csrfCookieName = "csrf" csrfCookieName = "csrf"
csrfFieldName = "csrf_token" csrfFieldName = "csrf_token"
csrfKey = []byte("7Oj4DllZ9lTsxJnisTuWiiQBGQIzi6gX") csrfKey = securecookie.GenerateRandomKey(32)
cookieKey = []byte("osx70sMD8HZG2ouUl8uKI4wcMugiJ2WH")
) )
func HandleRoutes(provider *services.Provider) http.Handler { func HandleRoutes(provider *services.Provider) http.Handler {
@ -34,7 +33,7 @@ func HandleRoutes(provider *services.Provider) http.Handler {
mux.Use(middleware.RealIP) // use proxy headers mux.Use(middleware.RealIP) // use proxy headers
mux.Use(middleware.RedirectSlashes) // redirect trailing slashes mux.Use(middleware.RedirectSlashes) // redirect trailing slashes
mux.Use(mw.Recoverer) // recover on panic mux.Use(mw.Recoverer) // recover on panic
mux.Use(provider.Sessions.Manager.Use) // use session storage mux.Use(provider.Sessions.LoadAndSave) // use session storage
// TODO: move this code away from here // TODO: move this code away from here
oauth2Config := &oauth2.Config{ oauth2Config := &oauth2.Config{

View file

@ -2,6 +2,7 @@ package services
import ( import (
"encoding/gob" "encoding/gob"
"errors"
"fmt" "fmt"
"html/template" "html/template"
"log" "log"
@ -11,7 +12,7 @@ import (
"github.com/alexedwards/scs" "github.com/alexedwards/scs"
) )
var ( const (
// FlashesKey is the key used for the flashes in the cookie // FlashesKey is the key used for the flashes in the cookie
FlashesKey = "_flashes" FlashesKey = "_flashes"
// UserEmailKey is the key used to reference usernames // UserEmailKey is the key used to reference usernames
@ -24,29 +25,24 @@ func init() {
} }
type SessionsConfig struct { type SessionsConfig struct {
SessionName string HTTPOnly bool
CookieKey string Secure bool
HttpOnly bool Lifetime time.Duration
Secure bool
Lifetime time.Duration
} }
// Sessions is a wrapped scs.Store in order to implement custom logic // Sessions is a wrapped scs.Store in order to implement custom logic
type Sessions struct { type Sessions struct {
*scs.Manager *scs.Session
} }
// NewSessions populates the default sessions Store // NewSessions populates the default sessions Store
func NewSessions(conf *SessionsConfig) *Sessions { func NewSessions(conf *SessionsConfig) *Sessions {
store := scs.NewCookieManager( session := scs.NewSession()
conf.CookieKey, session.Lifetime = conf.Lifetime
) session.Cookie.HttpOnly = true
store.Name(conf.SessionName) session.Cookie.Secure = conf.Secure
store.HttpOnly(true)
store.Lifetime(conf.Lifetime)
store.Secure(conf.Secure)
return &Sessions{store} return &Sessions{session}
} }
func (store *Sessions) GetUsername(req *http.Request) string { func (store *Sessions) GetUsername(req *http.Request) string {
@ -56,17 +52,8 @@ func (store *Sessions) GetUsername(req *http.Request) string {
return "" return ""
} }
sess := store.Load(req) email := store.GetString(req.Context(), UserEmailKey)
return email // "" if no user is logged in
email, err := sess.GetString(UserEmailKey)
if err != nil {
// Username found
return ""
}
// User is logged in
return email
} }
func (store *Sessions) SetUsername(w http.ResponseWriter, req *http.Request, username string) { func (store *Sessions) SetUsername(w http.ResponseWriter, req *http.Request, username string) {
@ -75,13 +62,10 @@ func (store *Sessions) SetUsername(w http.ResponseWriter, req *http.Request, use
return return
} }
sess := store.Load(req)
// renew token to avoid session pinning/fixation attack // renew token to avoid session pinning/fixation attack
sess.RenewToken(w) store.RenewToken(req.Context())
sess.PutString(w, UserEmailKey, username)
store.Put(req.Context(), UserEmailKey, username)
} }
type Flash struct { type Flash struct {
@ -101,23 +85,20 @@ func (flash Flash) Render() template.HTML {
// Flash add flash message to session data // Flash add flash message to session data
func (store *Sessions) Flash(w http.ResponseWriter, req *http.Request, flash Flash) error { func (store *Sessions) Flash(w http.ResponseWriter, req *http.Request, flash Flash) error {
var flashes []Flash flashes, ok := store.Get(req.Context(), FlashesKey).([]Flash)
if !ok {
sess := store.Load(req) return errors.New("Could not get flashes")
if err := sess.GetObject(FlashesKey, &flashes); err != nil {
return err
} }
flashes = append(flashes, flash) flashes = append(flashes, flash)
return nil
return sess.PutObject(w, FlashesKey, flashes)
} }
// Flashes returns a slice of flash messages from session data // Flashes returns a slice of flash messages from session data
func (store *Sessions) Flashes(w http.ResponseWriter, req *http.Request) []Flash { func (store *Sessions) Flashes(w http.ResponseWriter, req *http.Request) []Flash {
var flashes []Flash flashes, ok := store.Pop(req.Context(), FlashesKey).([]Flash)
sess := store.Load(req) if !ok {
sess.PopObject(w, FlashesKey, &flashes) return nil
}
return flashes return flashes
} }