fix: add csrf and remove need of a cache

This commit is contained in:
Johan Siebens
2022-09-03 17:33:22 +02:00
parent 41827dcdcd
commit d735974406
6 changed files with 62 additions and 49 deletions
-1
View File
@@ -18,7 +18,6 @@ require (
github.com/mr-tron/base58 v1.2.0 github.com/mr-tron/base58 v1.2.0
github.com/muesli/coral v1.0.0 github.com/muesli/coral v1.0.0
github.com/nleeper/goment v1.4.4 github.com/nleeper/goment v1.4.4
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/rodaine/table v1.0.1 github.com/rodaine/table v1.0.1
github.com/sony/sonyflake v1.0.0 github.com/sony/sonyflake v1.0.0
github.com/xhit/go-str2duration/v2 v2.0.0 github.com/xhit/go-str2duration/v2 v2.0.0
-2
View File
@@ -260,8 +260,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nleeper/goment v1.4.4 h1:GlMTpxvhueljArSunzYjN9Ri4SOmpn0Vh2hg2z/IIl8= github.com/nleeper/goment v1.4.4 h1:GlMTpxvhueljArSunzYjN9Ri4SOmpn0Vh2hg2z/IIl8=
github.com/nleeper/goment v1.4.4/go.mod h1:zDl5bAyDhqxwQKAvkSXMRLOdCowrdZz53ofRJc4VhTo= github.com/nleeper/goment v1.4.4/go.mod h1:zDl5bAyDhqxwQKAvkSXMRLOdCowrdZz53ofRJc4VhTo=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+50 -44
View File
@@ -5,9 +5,9 @@ import (
"encoding/json" "encoding/json"
"github.com/jsiebens/ionscale/internal/addr" "github.com/jsiebens/ionscale/internal/addr"
"github.com/jsiebens/ionscale/internal/provider" "github.com/jsiebens/ionscale/internal/provider"
"github.com/labstack/echo/v4/middleware"
"github.com/mr-tron/base58" "github.com/mr-tron/base58"
"net/http" "net/http"
"strconv"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"time" "time"
@@ -15,7 +15,6 @@ import (
"github.com/jsiebens/ionscale/internal/domain" "github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/internal/util" "github.com/jsiebens/ionscale/internal/util"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/patrickmn/go-cache"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
) )
@@ -26,29 +25,30 @@ func NewAuthenticationHandlers(
repository domain.Repository) *AuthenticationHandlers { repository domain.Repository) *AuthenticationHandlers {
return &AuthenticationHandlers{ return &AuthenticationHandlers{
config: config, config: config,
authProvider: authProvider, authProvider: authProvider,
repository: repository, repository: repository,
systemIAMPolicy: systemIAMPolicy, systemIAMPolicy: systemIAMPolicy,
pendingOAuthUsers: cache.New(5*time.Minute, 10*time.Minute),
} }
} }
type AuthenticationHandlers struct { type AuthenticationHandlers struct {
repository domain.Repository repository domain.Repository
authProvider provider.AuthProvider authProvider provider.AuthProvider
config *config.Config config *config.Config
systemIAMPolicy *domain.IAMPolicy systemIAMPolicy *domain.IAMPolicy
pendingOAuthUsers *cache.Cache
} }
type AuthFormData struct { type AuthFormData struct {
ProviderAvailable bool ProviderAvailable bool
Csrf string
} }
type TailnetSelectionData struct { type TailnetSelectionData struct {
AccountID uint64
Tailnets []domain.Tailnet Tailnets []domain.Tailnet
SystemAdmin bool SystemAdmin bool
Csrf string
} }
type oauthState struct { type oauthState struct {
@@ -86,7 +86,8 @@ func (h *AuthenticationHandlers) StartAuth(c echo.Context) error {
return c.Redirect(http.StatusFound, "/a/error") return c.Redirect(http.StatusFound, "/a/error")
} }
return c.Render(http.StatusOK, "auth.html", &AuthFormData{ProviderAvailable: h.authProvider != nil}) csrf := c.Get(middleware.DefaultCSRFConfig.ContextKey).(string)
return c.Render(http.StatusOK, "auth.html", &AuthFormData{ProviderAvailable: h.authProvider != nil, Csrf: csrf})
} }
func (h *AuthenticationHandlers) ProcessAuth(c echo.Context) error { func (h *AuthenticationHandlers) ProcessAuth(c echo.Context) error {
@@ -165,9 +166,13 @@ func (h *AuthenticationHandlers) Callback(c echo.Context) error {
return err return err
} }
h.pendingOAuthUsers.Set(state.Key, account, cache.DefaultExpiration) csrf := c.Get(middleware.DefaultCSRFConfig.ContextKey).(string)
return c.Render(http.StatusOK, "tailnets.html", &TailnetSelectionData{
return c.Render(http.StatusOK, "tailnets.html", &TailnetSelectionData{Tailnets: tailnets, SystemAdmin: isSystemAdmin}) Csrf: csrf,
Tailnets: tailnets,
SystemAdmin: isSystemAdmin,
AccountID: account.ID,
})
} }
func (h *AuthenticationHandlers) isSystemAdmin(ctx context.Context, u *provider.User) (bool, error) { func (h *AuthenticationHandlers) isSystemAdmin(ctx context.Context, u *provider.User) (bool, error) {
@@ -232,20 +237,30 @@ func (h *AuthenticationHandlers) Error(c echo.Context) error {
return c.Render(http.StatusOK, "error.html", nil) return c.Render(http.StatusOK, "error.html", nil)
} }
type TailnetSelectionForm struct {
AccountID uint64 `form:"aid"`
TailnetID uint64 `form:"tid"`
AsSystemAdmin bool `form:"sad"`
AuthKey string `form:"ak"`
}
func (h *AuthenticationHandlers) endCliAuthenticationFlow(c echo.Context, req *domain.AuthenticationRequest, state *oauthState) error { func (h *AuthenticationHandlers) endCliAuthenticationFlow(c echo.Context, req *domain.AuthenticationRequest, state *oauthState) error {
ctx := c.Request().Context() ctx := c.Request().Context()
item, ok := h.pendingOAuthUsers.Get(state.Key) var form TailnetSelectionForm
if !ok { if err := c.Bind(&form); err != nil {
return c.Redirect(http.StatusFound, "/a/error") return c.Redirect(http.StatusFound, "/a/error")
} }
oa := item.(*domain.Account) account, err := h.repository.GetAccount(ctx, form.AccountID)
if err != nil {
return c.Redirect(http.StatusFound, "/a/error")
}
// continue as system admin? // continue as system admin?
if c.FormValue("a") == "true" { if form.AsSystemAdmin {
expiresAt := time.Now().Add(24 * time.Hour) expiresAt := time.Now().Add(24 * time.Hour)
token, apiKey := domain.CreateSystemApiKey(oa, &expiresAt) token, apiKey := domain.CreateSystemApiKey(account, &expiresAt)
req.Token = token req.Token = token
err := h.repository.Transaction(func(rp domain.Repository) error { err := h.repository.Transaction(func(rp domain.Repository) error {
@@ -263,18 +278,12 @@ func (h *AuthenticationHandlers) endCliAuthenticationFlow(c echo.Context, req *d
return c.Redirect(http.StatusFound, "/a/success") return c.Redirect(http.StatusFound, "/a/success")
} }
tailnetIDParam := c.FormValue("s") tailnet, err := h.repository.GetTailnet(ctx, form.TailnetID)
parseUint, err := strconv.ParseUint(tailnetIDParam, 10, 64)
if err != nil {
return err
}
tailnet, err := h.repository.GetTailnet(ctx, parseUint)
if err != nil { if err != nil {
return err return err
} }
user, _, err := h.repository.GetOrCreateUserWithAccount(ctx, tailnet, oa) user, _, err := h.repository.GetOrCreateUserWithAccount(ctx, tailnet, account)
if err != nil { if err != nil {
return err return err
} }
@@ -302,8 +311,10 @@ func (h *AuthenticationHandlers) endCliAuthenticationFlow(c echo.Context, req *d
func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, registrationRequest *domain.RegistrationRequest, state *oauthState) error { func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, registrationRequest *domain.RegistrationRequest, state *oauthState) error {
ctx := c.Request().Context() ctx := c.Request().Context()
authKeyParam := c.FormValue("ak") var form TailnetSelectionForm
tailnetIDParam := c.FormValue("s") if err := c.Bind(&form); err != nil {
return c.Redirect(http.StatusFound, "/a/error")
}
req := tailcfg.RegisterRequest(registrationRequest.Data) req := tailcfg.RegisterRequest(registrationRequest.Data)
machineKey := registrationRequest.MachineKey machineKey := registrationRequest.MachineKey
@@ -313,10 +324,9 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
var user *domain.User var user *domain.User
var ephemeral bool var ephemeral bool
var tags = []string{} var tags = []string{}
//var expiryDisabled bool
if authKeyParam != "" { if form.AuthKey != "" {
authKey, err := h.repository.LoadAuthKey(ctx, authKeyParam) authKey, err := h.repository.LoadAuthKey(ctx, form.AuthKey)
if err != nil { if err != nil {
return err return err
} }
@@ -338,27 +348,23 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, regi
tags = authKey.Tags tags = authKey.Tags
ephemeral = authKey.Ephemeral ephemeral = authKey.Ephemeral
} else { } else {
parseUint, err := strconv.ParseUint(tailnetIDParam, 10, 64) selectedTailnet, err := h.repository.GetTailnet(ctx, form.TailnetID)
if err != nil {
return err
}
tailnet, err = h.repository.GetTailnet(ctx, parseUint)
if err != nil { if err != nil {
return err return err
} }
item, ok := h.pendingOAuthUsers.Get(state.Key) account, err := h.repository.GetAccount(ctx, form.AccountID)
if !ok { if err != nil {
return c.Redirect(http.StatusFound, "/a/error") return c.Redirect(http.StatusFound, "/a/error")
} }
oa := item.(*domain.Account) selectedUser, _, err := h.repository.GetOrCreateUserWithAccount(ctx, tailnet, account)
user, _, err = h.repository.GetOrCreateUserWithAccount(ctx, tailnet, oa)
if err != nil { if err != nil {
return err return err
} }
user = selectedUser
tailnet = selectedTailnet
ephemeral = false ephemeral = false
} }
+4
View File
@@ -17,6 +17,7 @@ import (
"github.com/jsiebens/ionscale/internal/templates" "github.com/jsiebens/ionscale/internal/templates"
echo_prometheus "github.com/labstack/echo-contrib/prometheus" echo_prometheus "github.com/labstack/echo-contrib/prometheus"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/h2c" "golang.org/x/net/http2/h2c"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@@ -130,6 +131,9 @@ func Start(c *config.Config) error {
tlsAppHandler.POST("/machine/:id/map", pollNetMapHandler.PollNetMap) tlsAppHandler.POST("/machine/:id/map", pollNetMapHandler.PollNetMap)
auth := tlsAppHandler.Group("/a") auth := tlsAppHandler.Group("/a")
auth.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "form:_csrf",
}))
auth.GET("/:key", authenticationHandlers.StartAuth) auth.GET("/:key", authenticationHandlers.StartAuth)
auth.POST("/:key", authenticationHandlers.ProcessAuth) auth.POST("/:key", authenticationHandlers.ProcessAuth)
auth.GET("/c/:key", authenticationHandlers.StartCliAuth) auth.GET("/c/:key", authenticationHandlers.StartCliAuth)
+2
View File
@@ -80,6 +80,7 @@
<small>Login with:</small> <small>Login with:</small>
</div> </div>
<form method="post"> <form method="post">
<input type="hidden" name="_csrf" value="{{.Csrf}}">
<ul class="selectionList"> <ul class="selectionList">
<li><button type="submit" name="s" value="true">OpenID</button></li> <li><button type="submit" name="s" value="true">OpenID</button></li>
</ul> </ul>
@@ -95,6 +96,7 @@
</div> </div>
{{end}} {{end}}
<form method="post" style="text-align: right"> <form method="post" style="text-align: right">
<input type="hidden" name="_csrf" value="{{.Csrf}}">
<p><input id="ak" name="ak" type="text"/></p> <p><input id="ak" name="ak" type="text"/></p>
<div style="padding-top: 10px"> <div style="padding-top: 10px">
<button type="submit">submit</button> <button type="submit">submit</button>
+6 -2
View File
@@ -80,8 +80,10 @@
<small>You are a member of the System Admin group:</small> <small>You are a member of the System Admin group:</small>
</div> </div>
<form method="post"> <form method="post">
<input type="hidden" name="aid" value="{{.AccountID}}">
<input type="hidden" name="_csrf" value="{{.Csrf}}">
<ul class="selectionList"> <ul class="selectionList">
<li><button type="submit" name="a" value="true">OK, continue as System Admin</button></li> <li><button type="submit" name="sad" value="true">OK, continue as System Admin</button></li>
</ul> </ul>
</form> </form>
{{end}} {{end}}
@@ -98,9 +100,11 @@
</div> </div>
{{end}} {{end}}
<form method="post"> <form method="post">
<input type="hidden" name="aid" value="{{.AccountID}}">
<input type="hidden" name="_csrf" value="{{.Csrf}}">
<ul class="selectionList"> <ul class="selectionList">
{{range .Tailnets}} {{range .Tailnets}}
<li><button type="submit" name="s" value="{{.ID}}">{{.Name}}</button></li> <li><button type="submit" name="tid" value="{{.ID}}">{{.Name}}</button></li>
{{end}} {{end}}
</ul> </ul>
</form> </form>