From f59f2bc6eb94bd24dfca626537aa1c48f35a29f2 Mon Sep 17 00:00:00 2001 From: "Schiefelbein, Andrew" Date: Thu, 3 Sep 2020 14:22:23 -0500 Subject: [PATCH] Update documentation for TLS and authentication Change-Id: Iac283b4feb17b5a9ddaf4d50d4fa53b77d29a4a3 --- Makefile | 10 +- README.md | 4 +- docs/img/authentication.jpg | Bin 0 -> 13431 bytes docs/source/developers.md | 71 ++--- examples/authentication/main.go | 262 ------------------- examples/authentication/templates/index.html | 86 ------ go.mod | 1 - tools/password.go | 46 ++++ 8 files changed, 90 insertions(+), 390 deletions(-) create mode 100755 docs/img/authentication.jpg delete mode 100644 examples/authentication/main.go delete mode 100644 examples/authentication/templates/index.html create mode 100755 tools/password.go diff --git a/Makefile b/Makefile index 8112aca..fcb346f 100755 --- a/Makefile +++ b/Makefile @@ -47,19 +47,11 @@ GO_FLAGS := -ldflags=$(LD_FLAGS) -trimpath BUILD_DIR := bin # Find all main.go files under cmd, excluding airshipui itself -EXAMPLE_NAMES := $(notdir $(subst /main.go,,$(wildcard examples/*/main.go))) -EXAMPLES := $(addprefix $(BUILD_DIR)/, $(EXAMPLE_NAMES)) MAIN := $(BUILD_DIR)/airshipui EXTENSION := -ifdef XDG_CONFIG_HOME - OCTANT_PLUGINSTUB_DIR ?= ${XDG_CONFIG_HOME}/octant/plugins -# Determine if on windows -else ifeq ($(OS),Windows_NT) - OCTANT_PLUGINSTUB_DIR ?= $(subst \,/,${LOCALAPPDATA}/octant/plugins) +ifeq ($(OS),Windows_NT) EXTENSION=.exe -else - OCTANT_PLUGINSTUB_DIR ?= ${HOME}/.config/octant/plugins endif DIRS = internal diff --git a/README.md b/README.md index 8816d80..1b2f3e2 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,14 @@ to go to a separate url or application. git clone https://opendev.org/airship/airshipui cd airshipui make # Note running behind a proxy can cause issues, notes on solving is in the Appendix of the Developer's Guide +bin/airshipui ``` +Once AirshipUI has started you should be able to browse to it at https://localhost:10443 ## Adding Additional Functionality Airship UI can be seamlessly integrated with service dashboards and other web-based tools by providing the necessary -configuration in $HOME/.airship/airshipui.json. +configuration in etc/airshipui.json. To add service dashboards, create a section at the top-level of airshipui.json as follows: diff --git a/docs/img/authentication.jpg b/docs/img/authentication.jpg new file mode 100755 index 0000000000000000000000000000000000000000..da3dade53007fdb7fed83013ab089388a5562277 GIT binary patch literal 13431 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:////" +Example webservice definition in etc/airshipui.json: +``` + "webservice": { + "host": "", + "port": , + "publicKey": "/.pem", + "privateKey": "/.pem" + }, +``` +### User Authentication +The UI uses Json Web Tokens ([JWT](https://tools.ietf.org/html/rfc7519)) to control access to the UI. The default method of generation is based on a userid and password enforcement +that the user is required to enter the first time accessing the UI. The UI will store the token locally and use it to authenticate the communication with the backend on every +transaction. + +The airshipui stores the user and password in etc/airshipui.json by default. The userid is clear text but the password is an non reversible sha512 hash of the password which is used to compare the supplied password with the expected password. No clear text passwords are stored. + +If no id and password is supplied the airshipui will create a default userid and password and store it in the etc/airshipui.json file so there will be no ability to use the UI +without a base id / password challenge authentication. + +The default userid is: admin The default password is: admin + +To generate a password you can run the password.go program in the tools directory: +``` +$ go run tools/password.go test_password +c8afeec4e9d29fa6307bc246965fe136a95bc47a9cfdedba0df256358eaa45ec0bf8d7a4333a4b13dc9a5508137d0f4d212272b27e64e41d4745a66b5f480759 +``` + +Example user definition in etc/airshipui.json: +``` + "users": { + "test": "c8afeec4e9d29fa6307bc246965fe136a95bc47a9cfdedba0df256358eaa45ec0bf8d7a4333a4b13dc9a5508137d0f4d212272b27e64e41d4745a66b5f480759" } +``` +After the user is defined in the etc/airshipui.json file the user can be used for authentication going forward. -Note: By default the system will start correctly without any authentication urls supplied to the configuration. -The expectation is that AirshipUI will be running in a minimal least authorized configuration. - -### Example Auth Server -There is an example authentication server in examples/authentication/main.go. These endpoints can be added to the -$HOME/.airshipui/airshipui.json and will allow the system to show a basic authentication test. -1. Basic auth on http://127.0.0.1:12321/basic-auth -2. Cookie based auth on http://127.0.0.1:12321/cookie -3. OAuth JWT (JSON Web Token) on http://127.0.0.1:12321/oauth - -To start the system cd to the root of the AirshipUI repository and execute: - - go run examples/authentication/main.go - -#### Example Auth Server Credentials -+ The example auth server id is: **airshipui** -+ The example auth server password is: **Open Sesame!** - +### Authentication decision tree +![AirshipUI Interactions](../img/authentication.jpg "AirshipUI Authentication Decision Tree") ## Behind the scenes diff --git a/examples/authentication/main.go b/examples/authentication/main.go deleted file mode 100644 index 9078cc3..0000000 --- a/examples/authentication/main.go +++ /dev/null @@ -1,262 +0,0 @@ -/* - 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 - - https://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 ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/base64" - "encoding/json" - "io" - "io/ioutil" - "log" - "net/http" - "text/template" - "time" - - "github.com/dgrijalva/jwt-go" -) - -// page struct is used for templated HTML -type page struct { - Title string -} - -// id and password passed from the test page -type authRequest struct { - ID string `json:"id,omitempty"` - Password string `json:"password,omitempty"` -} - -func main() { - // we're not picky, so we'll take everything and sort it out later - http.HandleFunc("/", handler) - - log.Println("Example Auth Server listening on :12321") - err := http.ListenAndServe(":12321", nil) - if err != nil { - log.Fatal(err) - } -} - -// URI check for /basic-auth, /cookie and /oauth, everything else gets a 404 -// Also a switch for GET and POST, everything else gets a 415 -func handler(w http.ResponseWriter, r *http.Request) { - method := r.Method - - uri := r.RequestURI - if uri == "/basic-auth" || uri == "/cookie" || uri == "/oauth" { - switch method { - case http.MethodGet: - get(uri, w) - case http.MethodPost: - post(uri, w, r) - default: - w.WriteHeader(http.StatusNotFound) - log.Printf("Method %s for %s being rejected, not implemented", method, uri) - } - } else { - w.WriteHeader(http.StatusNotFound) - log.Printf("URI %s being rejected, not found", uri) - } -} - -// handle the GET function and return a templated page -func get(uri string, w http.ResponseWriter) { - var p page - - switch uri { - case "/basic-auth": - p = page{ - Title: "Basic Auth", - } - case "/cookie": - p = page{ - Title: "Cookie", - } - case "/oauth": - p = page{ - Title: "OAuth", - } - } - - if p != (page{}) { - // parse and merge the template - err := template.Must(template.ParseFiles("./examples/authentication/templates/index.html")).Execute(w, p) - if err != nil { - w.Header().Set("Content-Type", "text/plain; charset=UTF-8") - log.Printf("Error getting the templated html: %v", err) - http.Error(w, "Error getting the templated html", http.StatusInternalServerError) - } - } -} - -// handle the POST function and return a mock authentication -func post(uri string, w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - log.Printf("Error reading body: %v", err) - http.Error(w, "can't read body", http.StatusBadRequest) - return - } - - var authAttempt authRequest - err = json.Unmarshal(body, &authAttempt) - - if err == nil { - // TODO: make the id and password part of a conf file somewhere - id := authAttempt.ID - passwd := authAttempt.Password - if id == "airshipui" && passwd == "Open Sesame!" { - w.WriteHeader(http.StatusCreated) - - response := map[string]interface{}{ - "id": id, - "name": "Some Name", - "expiration": time.Now().Add(time.Hour * 24).Unix(), - } - - switch uri { - case "/basic-auth": - response["X-Auth-Token"] = base64.StdEncoding.EncodeToString([]byte(id + ":" + passwd)) - response["type"] = "basic-auth" - postHelper(response, w) - case "/cookie": - response["type"] = "cookie" - cookieHandler(response, w) - case "/oauth": - response["type"] = "oauth" - jwtHandler(id, passwd, response, w) - } - } else { - w.Header().Set("Content-Type", "text/plain; charset=UTF-8") - http.Error(w, "Bad id or password", http.StatusUnauthorized) - } - } else { - w.Header().Set("Content-Type", "text/plain; charset=UTF-8") - log.Printf("Error unmarshalling the request: %v", err) - http.Error(w, "Error unmarshalling the request", http.StatusBadRequest) - } -} - -// potentially more complex logic happens here with cookie data -func cookieHandler(response map[string]interface{}, w http.ResponseWriter) { - cookie, err := json.Marshal(response) - if err != nil { - log.Printf("Error marshaling cookie response: %v", err) - } - b, err := encrypt(cookie) - if err != nil { - log.Printf("Error encrypting cookie response: %v", err) - postHelper(nil, w) - } else { - response["cookie"] = b - postHelper(response, w) - } -} - -// potentially more complex logic happens here with JWT data -func jwtHandler(id string, passwd string, response map[string]interface{}, w http.ResponseWriter) { - token, err := createToken(id, passwd) - if err != nil { - log.Printf("Error creating JWT token: %v", err) - postHelper(nil, w) - } else { - response["jwt"] = token - postHelper(response, w) - } -} - -// Helper function to reduce the number of error checks that have to happen in other functions -func postHelper(returnData map[string]interface{}, w http.ResponseWriter) { - if returnData == nil { - http.Error(w, "Internal error", http.StatusInternalServerError) - } else { - log.Printf("Auth data %s\n", returnData) - b, err := json.Marshal(returnData) - if err != nil { - log.Printf("Error marshaling the response: %v", err) - http.Error(w, "Internal error", http.StatusInternalServerError) - } else { - _, err := w.Write(b) - if err != nil { - log.Printf("Error sending POST response to client: %v", err) - } else { - go notifyElectron(b) - } - } - } -} - -// This is intended to send an auth completed message to the system so that it knows there was a successful login -func notifyElectron(data []byte) { - // TODO: probably need to pull the electron url out into its own - resp, err := http.Post("http://localhost:8080/auth", "application/json; charset=UTF-8", bytes.NewBuffer(data)) - if err != nil { - log.Printf("Error sending auth complete to electron. The response is %v, the error is %v\n", resp, err) - } -} - -// aes requires a 32 byte key, this is random for demo purposes -func randBytes(length int) ([]byte, error) { - b := make([]byte, length) - _, err := rand.Read(b) - if err != nil { - return nil, err - } - return b, nil -} - -// this creates a random ciphertext for demo purposes -// this is not intended to be reverseable or to be used in production -func encrypt(data []byte) ([]byte, error) { - b, err := randBytes(256 / 8) - if err != nil { - return nil, err - } - block, err := aes.NewCipher(b) - if err != nil { - return nil, err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - nonce := make([]byte, gcm.NonceSize()) - if _, err = io.ReadFull(rand.Reader, nonce); err != nil { - return nil, err - } - ciphertext := gcm.Seal(nonce, nonce, data, nil) - return ciphertext, nil -} - -// create a JWT (JSON Web Token) for demo purposes, this is not to be used in production -func createToken(id string, passwd string) (string, error) { - // create the token - token := jwt.New(jwt.SigningMethodHS256) - - // set some claims - claims := make(jwt.MapClaims) - claims["username"] = id - claims["password"] = passwd - claims["exp"] = time.Now().Add(time.Hour * 24).Unix() - - token.Claims = claims - - //Sign and get the complete encoded token as string - return (token.SignedString([]byte("airshipui"))) -} diff --git a/examples/authentication/templates/index.html b/examples/authentication/templates/index.html deleted file mode 100644 index ecd0068..0000000 --- a/examples/authentication/templates/index.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - AirshipUI Test {{.Title}} - - - - - -

Airship UI Test {{.Title}}

- - - - - - - - - - - - -
- Id:   - - -
- Password:   - - -
- -
-
-

⚠ Warning! ⚠

-

This is a {{.Title}} test page is only intended as an example for how to use {{.Title}} with AirshipUI.

-

The System will return the following HTML status codes and responses

-
    - {{if eq .Title "Basic Auth"}} -
  • 201: Created. The password attempt was successful and the backend has sent an xauth token header to AirshipUI.
  • - {{else if eq .Title "Cookie"}} -
  • 201: Created. The password attempt was successful and the backend has set a cookie and sent the cookie contents to AirshipUI.
  • - {{else if eq .Title "OAuth"}} -
  • 201: Created. The password attempt was successful and the backend has set a JWT (JSON Web Token) and sent the JWT contents to AirshipUI.
  • - {{end}} -
  • 400: Bad request. There was an error sending the system the authentication request, most likely bad JSON.
  • -
  • 401: Unauthorized. Bad id / password attempt.
  • -
  • 403: Forbidden. The id / password combination was correct but the id is not allowed for the resource.
  • -
  • 500: Internal Server Error. There was a processing error on the back end.
  • -
- - - \ No newline at end of file diff --git a/go.mod b/go.mod index 814e2a3..a25cc76 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module opendev.org/airship/airshipui go 1.13 require ( - github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/google/uuid v1.1.1 github.com/gorilla/websocket v1.4.2 github.com/pkg/errors v0.9.1 diff --git a/tools/password.go b/tools/password.go new file mode 100755 index 0000000..735e03f --- /dev/null +++ b/tools/password.go @@ -0,0 +1,46 @@ +/* + 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 + + https://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 ( + "crypto/sha512" + "fmt" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "go run password.go ", + Short: "Create an sha512 password hash", + Long: "This creates an sha512 password hash used for user authentication in the etc/airshipui.json conf file", + Run: launch, +} + +// take the password argument and turn it into a hash +func launch(cmd *cobra.Command, args []string) { + if len(args) == 1 { + // create and disply the sha512 hash for the password + hash := sha512.New() + hash.Write([]byte(args[0])) + fmt.Printf("%x\n", hash.Sum(nil)) + } else { + fmt.Println("There should be 1 password argument") + } +} + +func main() { + rootCmd.Execute() +}