Skip to content

Commit

Permalink
Added API protected key
Browse files Browse the repository at this point in the history
  • Loading branch information
wesuuu committed Nov 12, 2021
1 parent c9b5e62 commit 7fa04fa
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 101 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/
# && apt-get -y install --no-install-recommends <your-package-list-here>

# [Optional] Uncomment the next line to use go get to install anything else you need
RUN go get -x github.com/google/go-cmp/cmp gopkg.in/yaml.v2 github.com/gorilla/mux
RUN go get -x github.com/google/go-cmp/cmp gopkg.in/yaml.v2 github.com/gorilla/mux github.com/ghodss/yaml

COPY . /workspaces/deployment-controller
# [Optional] Uncomment this line to install global node packages.
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,25 @@ For example,
The above configuration states that for 90% of `PUT`s to `/` with `{"app": "jupyterhub"}` will return `{"Key": "a", "Value": "a", Expiration: <48 hours from now>}`. For the 10% case, the server will return `{"Key": "b", "Value": "b", Expiration: <48 hours from now>}`.

*Note* config is hot loaded every time `/` is called, so modifying the config will load all new changes upon the next request.


```
Apps:
- CookieInfo:
CanaryPercent: 0.9
Expiration: 48h
IfFail:
Key: b
Value: b
IfSuccessful:
Key: a
Value: a
Disable: false
Logging:
Disable: false
Name: jupyterhub
View:
ShowFail: true
ShowSuccess: true
Port: 8080
```
20 changes: 20 additions & 0 deletions auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"net/http"
"os"
)

func ApiKeyAuthMiddleware(next http.Handler) http.Handler {
apiKey := os.Getenv("API_KEY")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
token := r.Header.Get("Authorization")
if token != apiKey {
http.Error(w, "Invalid API Key", http.StatusForbidden)
} else {
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
}
})
}
96 changes: 96 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package main

import (
"errors"
"io/ioutil"
"os"

"github.com/ghodss/yaml"
)

const configFile = "/workspaces/deployment-controller/deployment-controller.yaml"

type KeyValue struct {
Key string `yaml:"Key"`
Value string `yaml:"Value"`
}

// cookie data from config file
type Cookie struct {
Expiration string `yaml:"Expiration"`
CanaryPercent float32 `yaml:"CanaryPercent"`
IfSuccessful KeyValue `yaml:"IfSuccessful"`
IfFail KeyValue `yaml:"IfFail"`
}

type View struct {
ShowSuccess bool `yaml:"ShowSuccess"`
ShowFail bool `yaml:"ShowFail"`
}

type Logging struct {
Disable bool `yaml:"Disable"`
}

type App struct {
Name string `yaml:"Name"`
Disable bool `yaml:"Disable"`
CookieInfo Cookie `yaml:"CookieInfo"`
View View `yaml:"View"`
Logging Logging `yaml:"Logging"`
}

type Config struct {
Apps []App `yaml:"Apps"`
Port int `yaml:"Port"`
}

func ReadConfig() (Config, error) {
// set path, use /workspaces/.. if unspecified
configPath := os.Getenv("APP_CONFIG_PATH")
if configPath == "" {
configPath = "/workspaces/deployment-controller/deployment-controller.yaml"
}
config := Config{}
configYaml, err := ioutil.ReadFile(configPath)
if err != nil {
return config, err
}

err = yaml.Unmarshal(configYaml, &config)
if err != nil {
return config, err
}

return config, nil
}

func UpdateConfig(app App) error {
config, err := ReadConfig()
if err != nil {
return err
}

appFound := false
for i, configapp := range config.Apps {
if app.Name == configapp.Name {
configapp = app
config.Apps[i] = configapp
appFound = true
}
}

if !appFound {
return errors.New("could not find app")
}

data, err := yaml.Marshal(config)
if err != nil {
return err
}
err = ioutil.WriteFile(configFile, data, 0)
if err != nil {
return err
}
return nil
}
36 changes: 18 additions & 18 deletions deployment-controller.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
apps:
- appName: jupyterhub
disable: false
cookieInfo:
expiration: 48h
canaryPercent: .90
ifSuccessful: # less than percent
key: a
value: a
ifFail:
key: b
value: b
view: # related to showing the UI elements to users (in this case, the stationary banner on datahub)
showSuccess: true
showFail: true
logging:
disable: false
port: 8080
Apps:
- CookieInfo:
CanaryPercent: 0.9
Expiration: 48h
IfFail:
Key: b
Value: b
IfSuccessful:
Key: a
Value: a
Disable: false
Logging:
Disable: false
Name: jupyterhub
View:
ShowFail: true
ShowSuccess: true
Port: 8080
1 change: 1 addition & 0 deletions example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
curl -XPUT -H "Authorization: test" -d '{"CookieInfo":{"CanaryPercent":0.9,"Expiration":"48h","IfFail":{"Key":"b","Value":"b"},"IfSuccessful":{"Key":"a","Value":"a"}},"Disable":false,"Logging":{"Disable":false},"Name":"jupyterhub","View":{"ShowFail":false,"ShowSuccess":true}}' http://localhost:8080/admin/jupyterhub
87 changes: 28 additions & 59 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,16 @@ package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"strconv"
"strings"
"time"

"github.com/gorilla/mux"
"gopkg.in/yaml.v2"
)

type KeyValue struct {
Key string `yaml:"key"`
Value string `yaml:"value"`
}

// cookie data from config file
type Cookie struct {
Expiration string `yaml:"expiration"`
CanaryPercent float32 `yaml:"canaryPercent"`
IfSuccessful KeyValue `yaml:"ifSuccessful"`
IfFail KeyValue `yaml:"ifFail"`
}

type View struct {
ShowSuccess bool `yaml:"showSuccess"`
ShowFail bool `yaml:"showFail"`
}

type Logging struct {
Disable bool `yaml:"disable"`
}

type App struct {
Name string `yaml:"appName"`
Disable bool `yaml:"disable"`
CookieInfo Cookie `yaml:"cookieInfo"`
View View `yaml:"view"`
Logging Logging `yaml:"logging"`
}

type Config struct {
Apps []App `yaml:"apps"`
Port int `yaml:"port"`
}

type CookieResponse struct {
Key string
Value string
Expand All @@ -60,26 +22,6 @@ type CookieResponse struct {
Disable bool
}

func ReadConfig() (Config, error) {
// set path, use /workspaces/.. if unspecified
configPath := os.Getenv("APP_CONFIG_PATH")
if configPath == "" {
configPath = "/workspaces/deployment-controller/deployment-controller.yaml"
}
config := Config{}
configYaml, err := ioutil.ReadFile(configPath)
if err != nil {
return config, err
}

err = yaml.Unmarshal(configYaml, &config)
if err != nil {
return config, err
}

return config, nil
}

func GetCookieResponse(cookie Cookie, timeNow time.Time, successCookieType bool) (CookieResponse, error) {

hours := strings.TrimSuffix(cookie.Expiration, "h")
Expand Down Expand Up @@ -254,6 +196,24 @@ func GetLogging(w http.ResponseWriter, req *http.Request) {
respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Could not find app = %v", appName))
}

func UpdateAppConfig(w http.ResponseWriter, req *http.Request) {
decoder := json.NewDecoder(req.Body)
var app App
if err := decoder.Decode(&app); err != nil {
respondWithError(w, http.StatusBadRequest, "Could not decode app fields from body")
return
}

err := UpdateConfig(app)
if err != nil {
log.Println(err)
respondWithError(w, http.StatusInternalServerError, "Could not save config")
return
}

w.WriteHeader(http.StatusOK)
}

func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
Expand All @@ -268,13 +228,22 @@ func main() {
if err != nil {
log.Println(err)
}
router := mux.NewRouter()
router := mux.NewRouter() // public routes
protected := router.PathPrefix("/admin").Subrouter() // private

router.Use(loggingMiddleware)
protected.Use(loggingMiddleware)
protected.Use(ApiKeyAuthMiddleware)

router.Path("/apps/{app}").
Queries("cookie-type", "{cookie-type:success|fail}").
Methods("GET").
HandlerFunc(GetCookieByType)

protected.Path("/{app}").
Methods("PUT").
HandlerFunc(UpdateAppConfig)

router.Path("/apps/{app}/views").
Methods("GET").
HandlerFunc(GetViews)
Expand Down
Loading

0 comments on commit 7fa04fa

Please sign in to comment.