Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
devel:kubic
gangway
fix-securecookie-value-too-long.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File fix-securecookie-value-too-long.patch of Package gangway
diff --git a/Gopkg.lock b/Gopkg.lock index c36883f..af6de07 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -193,6 +193,7 @@ input-imports = [ "github.com/dgrijalva/jwt-go", "github.com/ghodss/yaml", + "github.com/gorilla/securecookie", "github.com/gorilla/sessions", "github.com/justinas/alice", "github.com/kelseyhightower/envconfig", diff --git a/Makefile b/Makefile index a467ef5..8f61f96 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ setup: go get -u github.com/golang/dep/cmd/dep go get -u github.com/mjibson/esc/... -check: test vet gofmt staticcheck unused misspell +check: test vet gofmt staticcheck misspell deps: dep ensure -v @@ -49,11 +49,7 @@ test: staticcheck: @go get honnef.co/go/tools/cmd/staticcheck - staticcheck $(PKGS) - -unused: - @go get honnef.co/go/tools/cmd/unused - unused -exported $(PKGS) + staticcheck -unused.whole-program $(PKGS) misspell: @go get github.com/client9/misspell/cmd/misspell diff --git a/internal/session/session.go b/internal/session/session.go index 93d3fc8..8c76260 100644 --- a/internal/session/session.go +++ b/internal/session/session.go @@ -16,23 +16,21 @@ package session import ( "crypto/sha256" - "net/http" - - "github.com/gorilla/sessions" "golang.org/x/crypto/pbkdf2" + "net/http" ) const salt = "MkmfuPNHnZBBivy0L0aW" // Session defines a Gangway session type Session struct { - Session *sessions.CookieStore + Session *CustomCookieStore } // New inits a Session with CookieStore func New(sessionSecurityKey string) *Session { return &Session{ - Session: sessions.NewCookieStore(generateSessionKeys(sessionSecurityKey)), + Session: NewCustomCookieStore(generateSessionKeys(sessionSecurityKey)), } } diff --git a/internal/session/store.go b/internal/session/store.go new file mode 100644 index 0000000..9b930c0 --- /dev/null +++ b/internal/session/store.go @@ -0,0 +1,135 @@ +// Copyright © 2019 Heptio +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package session + +import ( + "fmt" + "github.com/gorilla/securecookie" + "github.com/gorilla/sessions" + "net/http" +) + +// The CustomCookieStore automatically splits cookies with length greater than maxCookieLength into multiple smaller cookies. +// The motivation is the browsers' 4KB limit on cookies, which for instance causes problems for large id_tokens in azure. + +const ( + // Cookies are limited to 4kb including the length of the cookie name, + // the cookie name can be up to 256 bytes + maxCookieLength = 3840 +) + +type CustomCookieStore struct { + *sessions.CookieStore +} + +// Set secureCookie maxLength to an arbitrary (20x4kb) high value since we are no longer limited +func NewCustomCookieStore(keyPairs ...[]byte) *CustomCookieStore { + cookieStore := sessions.NewCookieStore(keyPairs...) + for _, codec := range cookieStore.Codecs { + cookie := codec.(*securecookie.SecureCookie) + cookie.MaxLength(81920) + } + return &CustomCookieStore{cookieStore} +} + +func (s *CustomCookieStore) Get(r *http.Request, name string) (*sessions.Session, error) { + return sessions.GetRegistry(r).Get(s, name) +} + +// In contrast to default implementation, the session values can be partitioned into +// multiple cookies. +// The original cookie is split/joined in its encoded form +func (s *CustomCookieStore) New(r *http.Request, name string) (*sessions.Session, error) { + session := sessions.NewSession(s, name) + opts := *s.Options + session.Options = &opts + session.IsNew = true + cookie := joinSectionCookies(r, name) + var err error + if len(cookie) > 0 { + err = securecookie.DecodeMulti(name, cookie, &session.Values, s.Codecs...) + if err == nil { + session.IsNew = false + } + } + return session, err +} + +// If the cookie length is > maxCookieLength, its value is split into multiple cookies +// fitting into the maxCookieLength limit. +// The resulting section cookies get their index appended to the name. +func (s *CustomCookieStore) Save(r *http.Request, w http.ResponseWriter, + session *sessions.Session) error { + + cookie, err := securecookie.EncodeMulti(session.Name(), session.Values, + s.Codecs...) + if err != nil { + return err + } + + sectionCookies := splitCookie(cookie) + // With a singular section the name is unchanged + if len(sectionCookies) == 1 { + cookieName := session.Name() + http.SetCookie(w, sessions.NewCookie(cookieName, sectionCookies[0], session.Options)) + return nil + } + + for i, value := range sectionCookies { + cookieName := buildSectionCookieName(session.Name(), i) + http.SetCookie(w, sessions.NewCookie(cookieName, value, session.Options)) + } + return nil +} + +// joinCookies concatenates the values of all matching cookies and returns the original, encoded cookievalue string. +func joinSectionCookies(r *http.Request, name string) string { + + // Exact match without index means only a single cookie exists + if c, err := r.Cookie(name); err == nil { + return c.Value + } + + var joinedValue string + for i := 0; true; i++ { + cookieName := buildSectionCookieName(name, i) + if c, err := r.Cookie(cookieName); err == nil { + joinedValue += c.Value + } else { + break + } + } + return joinedValue +} + +// splitCookie splits the original encoded cookie value into a slice of cookies which +// fit within the 4kb cookie limit indexing the cookies from 0 +func splitCookie(cookieValue string) []string { + var sectionCookies []string + valueBytes := []byte(cookieValue) + + for len(valueBytes) > 0 { + length := len(valueBytes) + if length > maxCookieLength { + length = maxCookieLength + } + sectionCookies = append(sectionCookies, string(valueBytes[:length])) + valueBytes = valueBytes[length:] + } + return sectionCookies +} + +func buildSectionCookieName(name string, index int) string { + return fmt.Sprintf("%s_%d", name, index) +} diff --git a/internal/session/store_test.go b/internal/session/store_test.go new file mode 100644 index 0000000..c010f8a --- /dev/null +++ b/internal/session/store_test.go @@ -0,0 +1,154 @@ +// Copyright © 2019 Heptio +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package session + +import ( + "fmt" + "github.com/gorilla/sessions" + log "github.com/sirupsen/logrus" + "math" + "math/rand" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestJoinSectionCookies(t *testing.T) { + var originalValue string + var value string + cookies := buildRandomCookies(2, 3800, "test_%d") + buildRequestWithCookies(cookies, func(cookies []*http.Cookie, r *http.Request) { + for _, c := range cookies { + originalValue += c.Value + } + value = joinSectionCookies(r, "test") + }) + if value != originalValue { + t.Errorf("joinSectionCookies value incorrect: \n value: %s \n originalValue: %s", value, originalValue) + } +} + +func TestJoinSectionCookiesSingle(t *testing.T) { + var originalValue string + var value string + cookies := buildRandomCookies(1, 2000, "test_%d") + buildRequestWithCookies(cookies, func(cookies []*http.Cookie, r *http.Request) { + for _, c := range cookies { + originalValue += c.Value + } + value = joinSectionCookies(r, "test") + }) + if value != originalValue { + t.Errorf("joinSectionCookies value incorrect: \n value: %s \n originalValue: %s", value, originalValue) + } +} + +func TestSplitCookie(t *testing.T) { + cookieLength := 8000 + originalValue := randStringBytesRmndr(cookieLength) + sectionCookies := splitCookie(originalValue) + expectedCount := int(math.Ceil((float64(cookieLength) / maxCookieLength))) + if len(sectionCookies) != expectedCount { + t.Errorf("splitCookie count incorrect: \n count: %d \n expectedCount: %d", len(sectionCookies), expectedCount) + } + value := strings.Join(sectionCookies, "") + if value != originalValue { + t.Errorf("splitCookie value incorrect: \n value: %s \n originalValue: %s", value, originalValue) + } +} + +func TestSplitCookieSingle(t *testing.T) { + cookieLength := 2000 + originalValue := randStringBytesRmndr(cookieLength) + sectionCookies := splitCookie(originalValue) + expectedCount := int(math.Ceil((float64(cookieLength) / maxCookieLength))) + if len(sectionCookies) != expectedCount { + t.Errorf("splitCookie count incorrect: \n count: %d \n expectedCount: %d", len(sectionCookies), expectedCount) + } +} + +func TestSplitCookieSize(t *testing.T) { + cookieLength := 10000 + originalValue := randStringBytesRmndr(cookieLength) + sectionCookies := splitCookie(originalValue) + for _, s := range sectionCookies { + if len(s) > maxCookieLength { + t.Errorf("sectionCookie length over limit: \n length: %d", len(s)) + } + } +} + +func TestSplitAndJoin(t *testing.T) { + cookieLength := 10000 + originalValue := randStringBytesRmndr(cookieLength) + sectionCookies := splitCookie(originalValue) + cookies := buildCookiesFromValues(sectionCookies, "test_%d") + var value string + buildRequestWithCookies(cookies, func(cookies []*http.Cookie, r *http.Request) { + value = joinSectionCookies(r, "test") + }) + if value != originalValue { + t.Errorf("SplitAndJoin value incorrect: \n value: %s \n originalValue: %s", value, originalValue) + } +} + +// Utility + +type handleReq func([]*http.Cookie, *http.Request) + +func buildRequestWithCookies(cookies []*http.Cookie, fn handleReq) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for _, cookie := range cookies { + r.AddCookie(cookie) + } + fn(cookies, r) + })) + defer ts.Close() + _, err := http.Get(ts.URL) + if err != nil { + log.Fatal(err) + } +} + +func buildRandomCookies(cookieCount int, cookieLength int, cookieName string) []*http.Cookie { + sessionOptions := &sessions.Options{} + var cookies []*http.Cookie + for i := 0; i < cookieCount; i++ { + value := randStringBytesRmndr(cookieLength) + cookie := sessions.NewCookie(fmt.Sprintf(cookieName, i), value, sessionOptions) + cookies = append(cookies, cookie) + } + return cookies +} + +func buildCookiesFromValues(values []string, cookieName string) []*http.Cookie { + sessionOptions := &sessions.Options{} + var cookies []*http.Cookie + for i, value := range values { + cookie := sessions.NewCookie(fmt.Sprintf(cookieName, i), value, sessionOptions) + cookies = append(cookies, cookie) + } + return cookies +} + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +func randStringBytesRmndr(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))] + } + return string(b) +}
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor