forked from kubeflow/kubeflow
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* [WIP] basic auth service * complete cookie setting and auth logic * fmt * add ksonnet lib; fix bugs * corrent redirect; update image * address review feedbacks * update comments
- Loading branch information
Showing
10 changed files
with
530 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
FROM golang:1.11.2 as bootstrap_base | ||
|
||
RUN mkdir -p /opt/kubeflow | ||
RUN mkdir -p $GOPATH/src/github.com/kubeflow/kubeflow/components/gatekeeper | ||
WORKDIR $GOPATH/src/github.com/kubeflow/kubeflow/components/gatekeeper | ||
|
||
ENV PATH /go/bin:/usr/local/go/bin:$PATH | ||
# use go modules | ||
ENV GO111MODULE=on | ||
|
||
COPY . . | ||
RUN go mod download | ||
RUN go build -gcflags 'all=-N -l' -o /opt/kubeflow/gatekeeper cmd/gatekeeper/main.go | ||
|
||
RUN chmod a+rx /opt/kubeflow/gatekeeper | ||
|
||
EXPOSE 8085 | ||
|
||
CMD ["/opt/kubeflow/gatekeeper"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
GCLOUD_PROJECT ?= kubeflow-images-public | ||
GOLANG_VERSION ?= 1.11.2 | ||
IMG ?= gcr.io/$(GCLOUD_PROJECT)/gatekeeper | ||
TAG ?= $(eval TAG := $(shell date +v%Y%m%d)-$(shell git describe --tags --always --dirty)-$(shell git diff | shasum -a256 | cut -c -6))$(TAG) | ||
|
||
build: | ||
docker build -t $(IMG):$(TAG) . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
## Http basic Auth for Kubeflow | ||
|
||
### To build image | ||
|
||
make build | ||
|
||
### Prerequisites | ||
|
||
golang to 1.11.2 | ||
|
||
```sh | ||
$ ☞ go version | ||
go version go1.11.2 darwin/amd64 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
// Copyright 2019 The Kubeflow Authors | ||
// | ||
// 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 auth | ||
|
||
import ( | ||
"encoding/base64" | ||
"fmt" | ||
"github.com/kubeflow/kubeflow/components/gatekeeper/cmd/gatekeeper/options" | ||
log "github.com/sirupsen/logrus" | ||
"golang.org/x/crypto/bcrypt" | ||
"math/rand" | ||
"net/http" | ||
"path" | ||
"strings" | ||
"sync" | ||
"time" | ||
) | ||
|
||
// start with easy case: each server struct only has one valid pair of u/p | ||
type authServer struct { | ||
username string | ||
// password bcrypt hash | ||
pwhash string | ||
// authorized cookies and their expire time (12 hour by default) | ||
cookies map[string]time.Time | ||
serverMux sync.Mutex | ||
allowHttp bool | ||
} | ||
|
||
const CookieName = "KUBEFLOW-AUTH-KEY" | ||
const LoginPagePath = "kflogin" | ||
const LoginPageHeader = "x-from-login" | ||
|
||
func NewAuthServer(opt *options.ServerOption) *authServer { | ||
data, err := base64.StdEncoding.DecodeString(opt.Pwhash) | ||
if err != nil { | ||
log.Fatal("error:", err) | ||
} | ||
server := &authServer{ | ||
username: opt.Username, | ||
pwhash: string(data), | ||
cookies: make(map[string]time.Time), | ||
allowHttp: opt.AllowHttp, | ||
} | ||
return server | ||
} | ||
|
||
// Default auth check service | ||
func (s *authServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
if (!s.allowHttp) && r.Header.Get("X-Forwarded-Proto") != "https" { | ||
log.Infof("Redirect http traffic.") | ||
// redirect to login page | ||
s.redirectToLogin(w, r) | ||
return | ||
} | ||
log.Infof("Path check, url: %v, path: %v", r.URL, r.URL.Path) | ||
// login page open to everyone; all other path requires auth with Password or cookie | ||
if strings.HasPrefix(r.URL.Path, "/" + LoginPagePath) || s.authCookie(r) == true { | ||
// Handle user's re-login | ||
// They already have auth cookie in browser, so "StatusResetContent" bring them to kubeflow central dashboard. | ||
if r.Header.Get(LoginPageHeader) != "" { | ||
w.WriteHeader(http.StatusResetContent) | ||
w.Write([]byte(http.StatusText(http.StatusResetContent))) | ||
return | ||
} | ||
// Allow browser request | ||
log.Infof("Allow browser request") | ||
w.WriteHeader(http.StatusOK) | ||
w.Write([]byte(http.StatusText(http.StatusOK))) | ||
return | ||
} | ||
|
||
if s.authpwd(r) == true { | ||
log.Infof("P/W passed") | ||
// Handle request from login page | ||
if r.Header.Get(LoginPageHeader) != "" { | ||
s.setCookieAndReset(w, r) | ||
return | ||
} | ||
// Allow requst from API call | ||
w.WriteHeader(http.StatusOK) | ||
w.Write([]byte(http.StatusText(http.StatusOK))) | ||
return | ||
} | ||
// If unauthorized request comes from login page, we skip redirect, just indicate username / password wrong. | ||
if r.Header.Get(LoginPageHeader) != "" { | ||
w.WriteHeader(http.StatusUnauthorized) | ||
w.Write([]byte(http.StatusText(http.StatusUnauthorized))) | ||
return | ||
} | ||
|
||
log.Infof("Unauthorized, redirect to %v", "https://" + path.Join(r.Host, LoginPagePath)) | ||
// redirect to login page | ||
s.redirectToLogin(w, r) | ||
} | ||
|
||
// auth with basic pw | ||
func (s *authServer) authpwd(r *http.Request) bool { | ||
auth := r.Header.Get("Authorization") | ||
if !strings.HasPrefix(strings.ToLower(auth), "basic ") { | ||
return false | ||
} | ||
|
||
upBytes, err := base64.StdEncoding.DecodeString(auth[len("basic "):]) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
namepw := strings.Split(string(upBytes), ":") | ||
|
||
if len(namepw) != 2 { | ||
return false | ||
} | ||
err = bcrypt.CompareHashAndPassword([]byte(s.pwhash), []byte(namepw[1])) | ||
if namepw[0] == s.username && err == nil { | ||
|
||
return true | ||
} | ||
return false | ||
} | ||
|
||
// auth with cookie | ||
func (s *authServer) authCookie(r *http.Request) bool { | ||
if cookie, err := r.Cookie(CookieName); err == nil { | ||
if val, ok := s.cookies[cookie.Value]; ok { | ||
if time.Now().Before(val) { | ||
log.Info("cookie auth: passed! %v", cookie.Value) | ||
return true | ||
} | ||
log.Info("cookie auth: cookie value expired!") | ||
return false | ||
} | ||
log.Info("cookie auth: cookie value not found! %v", cookie.Value) | ||
return false | ||
} | ||
log.Info("cookie auth: cookie does't exist in request!") | ||
return false | ||
} | ||
|
||
// redirect to login page when unauthorized | ||
func (s *authServer) redirectToLogin(w http.ResponseWriter, r *http.Request) { | ||
http.Redirect(w, r, "https://" + path.Join(r.Host, LoginPagePath), http.StatusTemporaryRedirect) | ||
} | ||
|
||
func generateCookieValue() string { | ||
chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" | ||
b := make([]byte, 20) | ||
for i := range b { | ||
b[i] = chars[rand.Intn(len(chars))] | ||
} | ||
return string(b) | ||
} | ||
|
||
func (s *authServer) addNewCookieValue(cookieVal string) { | ||
s.serverMux.Lock() | ||
defer s.serverMux.Unlock() | ||
// Cookie expire after 12 hours | ||
log.Info("cookie set: set new cookie value!") | ||
s.cookies[cookieVal] = time.Now().Add(12 * time.Hour) | ||
} | ||
|
||
// Set auth cookie and reset, UI will redirect to kubeflow central dashboard | ||
func (s *authServer) setCookieAndReset(w http.ResponseWriter, r *http.Request) { | ||
cookieVal := generateCookieValue() | ||
s.addNewCookieValue(cookieVal) | ||
cookie := http.Cookie{ | ||
Name: CookieName, | ||
Value: cookieVal, | ||
Expires: time.Now().Add(12 * time.Hour), | ||
Path: "/", | ||
// prevent cross-origin information leakage. | ||
SameSite: http.SameSiteStrictMode, | ||
} | ||
log.Info("set Cookie And Redirect!") | ||
http.SetCookie(w, &cookie) | ||
w.WriteHeader(http.StatusResetContent) | ||
w.Write([]byte(http.StatusText(http.StatusResetContent))) | ||
} | ||
|
||
func (s *authServer) Start(port int) { | ||
if port <= 0 { | ||
log.Fatal("port must be > 0.") | ||
} | ||
rand.Seed(time.Now().UTC().UnixNano()) | ||
log.Info("Auth Service starts") | ||
// All request | ||
http.Handle("/", s) | ||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil)) | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Copyright 2019 The Kubeflow Authors | ||
// | ||
// 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 main | ||
|
||
import ( | ||
"flag" | ||
"github.com/kubeflow/kubeflow/components/gatekeeper/auth" | ||
"github.com/kubeflow/kubeflow/components/gatekeeper/cmd/gatekeeper/options" | ||
"github.com/onrik/logrus/filename" | ||
log "github.com/sirupsen/logrus" | ||
) | ||
|
||
func init() { | ||
// Add filename as one of the fields of the structured log message | ||
filenameHook := filename.NewHook() | ||
filenameHook.Field = "filename" | ||
log.AddHook(filenameHook) | ||
} | ||
|
||
func main() { | ||
sop := options.NewServerOption() | ||
sop.AddFlags(flag.CommandLine) | ||
|
||
flag.Parse() | ||
if sop.Username == "" || sop.Pwhash == "" { | ||
log.Fatal("Username or Pwhash empty, exit now") | ||
} | ||
s := auth.NewAuthServer(sop) | ||
s.Start(8085) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Copyright 2019 The Kubeflow Authors | ||
// | ||
// 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 options | ||
|
||
import "flag" | ||
|
||
type ServerOption struct { | ||
Username string | ||
Pwhash string | ||
AllowHttp bool | ||
// Email for password reset? | ||
// Email string | ||
} | ||
|
||
func NewServerOption() *ServerOption { | ||
s := ServerOption{} | ||
return &s | ||
} | ||
|
||
func (s *ServerOption) AddFlags(fs *flag.FlagSet) { | ||
fs.StringVar(&s.Username, "username", "", "Username for login") | ||
fs.StringVar(&s.Pwhash, "pwhash", "", "Bcrypt hash of password for login.") | ||
fs.BoolVar(&s.AllowHttp, "allowhttp", false, "Whether or not allow http traffic. Http for test only") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module github.com/kubeflow/kubeflow/components/gatekeeper | ||
|
||
require ( | ||
github.com/onrik/logrus v0.2.1 | ||
github.com/sirupsen/logrus v1.3.0 | ||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||
github.com/onrik/logrus v0.2.1 h1:xEYR+opLvr+hNixPPAimuQppFYHaZ0XLO9hZ2G8WPLI= | ||
github.com/onrik/logrus v0.2.1/go.mod h1:qfe9NeZVAJfIxviw3cYkZo3kvBtLoPRJriAO8zl7qTk= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= | ||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | ||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= | ||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= | ||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
Oops, something went wrong.