You've already forked ionscale
mirror of
https://github.com/jsiebens/ionscale.git
synced 2026-04-05 12:32:58 +01:00
fix: add csrf and remove need of a cache
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user