Skip to content

Commit 8c543da

Browse files
author
Jeff McCormick
committed
add initial BasicAuth support
1 parent 7d64fd6 commit 8c543da

File tree

8 files changed

+214
-1
lines changed

8 files changed

+214
-1
lines changed

apiserver.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ func main() {
2020

2121
log.Infoln("postgres-operator apiserver starts")
2222
r := mux.NewRouter()
23+
r.HandleFunc("/authtest", versionservice.AuthTestHandler)
2324
r.HandleFunc("/version", versionservice.VersionHandler)
2425
r.HandleFunc("/clones", cloneservice.CreateCloneHandler)
2526
r.HandleFunc("/policies", policyservice.CreatePolicyHandler)
@@ -37,5 +38,6 @@ func main() {
3738
r.HandleFunc("/clusters/scale/{name}", clusterservice.ScaleClusterHandler)
3839
r.HandleFunc("/backups/{name}", backupservice.ShowBackupHandler).Methods("GET", "DELETE")
3940
r.HandleFunc("/backups", backupservice.CreateBackupHandler).Methods("POST")
41+
//log.Fatal(http.ListenAndServeTLS(":8080", "/cpmkeys/cert.pem", "/cpmkeys/key.pem", r))
4042
log.Fatal(http.ListenAndServe(":8080", r))
4143
}

apiserver/root.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616
*/
1717

1818
import (
19+
"bufio"
1920
"flag"
2021
log "github.com/Sirupsen/logrus"
2122
crdclient "github.com/crunchydata/postgres-operator/client"
@@ -24,8 +25,12 @@ import (
2425
"k8s.io/client-go/rest"
2526
"k8s.io/client-go/tools/clientcmd"
2627
"os"
28+
"strings"
2729
)
2830

31+
// pgouserPath ...
32+
const pgouserPath = "/config/pgouser"
33+
2934
// RESTClient ...
3035
var RESTClient *rest.RESTClient
3136

@@ -44,9 +49,14 @@ const TreeTrunk = "└── "
4449
// TreeBranch is for debugging only in this context
4550
const TreeBranch = "├── "
4651

52+
// Credentials holds the BasicAuth credentials found in the config
53+
var Credentials map[string]string
54+
4755
func init() {
4856
log.Infoln("apiserver starts")
4957

58+
getCredentials()
59+
5060
initConfig()
5161

5262
ConnectToKube()
@@ -131,3 +141,59 @@ func initConfig() {
131141
log.Debug("namespace is " + viper.GetString("Namespace"))
132142

133143
}
144+
145+
func file2lines(filePath string) []string {
146+
f, err := os.Open(filePath)
147+
if err != nil {
148+
log.Error(err)
149+
os.Exit(2)
150+
}
151+
defer f.Close()
152+
153+
var lines []string
154+
scanner := bufio.NewScanner(f)
155+
for scanner.Scan() {
156+
lines = append(lines, scanner.Text())
157+
}
158+
if err := scanner.Err(); err != nil {
159+
log.Error(err)
160+
}
161+
162+
return lines
163+
}
164+
165+
func parseUserMap(dat string) (string, string) {
166+
167+
fields := strings.Split(strings.TrimSpace(dat), ":")
168+
log.Infof("%v\n", fields)
169+
log.Infof("username=[%s] password=[%s]\n", fields[0], fields[1])
170+
return fields[0], fields[1]
171+
}
172+
173+
// getCredentials ...
174+
func getCredentials() {
175+
var Username, Password string
176+
177+
Credentials = make(map[string]string)
178+
179+
lines := file2lines(pgouserPath)
180+
for _, v := range lines {
181+
Username, Password = parseUserMap(v)
182+
log.Debugf("username=%s password=%s\n", Username, Password)
183+
Credentials[Username] = Password
184+
}
185+
186+
}
187+
188+
func BasicAuthCheck(username, password string) bool {
189+
value := Credentials[username]
190+
if value == "" {
191+
return false
192+
}
193+
194+
if value != password {
195+
return false
196+
}
197+
198+
return true
199+
}

apiserver/versionservice/versionservice.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ limitations under the License.
1818
import (
1919
"encoding/json"
2020
log "github.com/Sirupsen/logrus"
21+
"github.com/crunchydata/postgres-operator/apiserver"
2122
"net/http"
2223
)
2324

@@ -34,3 +35,33 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
3435

3536
json.NewEncoder(w).Encode(resp)
3637
}
38+
39+
// VersionHandler ...
40+
// pgo version
41+
func AuthTestHandler(w http.ResponseWriter, r *http.Request) {
42+
43+
log.Debug("versionservice.AuthTestHandler called")
44+
45+
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
46+
47+
username, password, authOK := r.BasicAuth()
48+
if authOK == false {
49+
http.Error(w, "Not authorized", 401)
50+
return
51+
}
52+
53+
log.Debugf("versionservice.AuthTestHandler username=[%s] password=[%s]\n", username, password)
54+
55+
if !apiserver.BasicAuthCheck(username, password) {
56+
//if username != "username" || password != "password" {
57+
http.Error(w, "Not authenticated in apiserver", 401)
58+
return
59+
}
60+
61+
w.WriteHeader(http.StatusOK)
62+
w.Header().Set("Content-Type", "application/json")
63+
64+
resp := Version()
65+
66+
json.NewEncoder(w).Encode(resp)
67+
}

conf/apiserver/pgouser

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
username:password
2+
testuser:testpass

deploy/deploy.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
1818
$DIR/cleanup.sh
1919

2020
$CO_CMD --namespace=$CO_NAMESPACE create configmap apiserver-conf \
21+
--from-file=$COROOT/conf/apiserver/pgouser \
2122
--from-file=$COROOT/conf/apiserver/pgo.yaml \
2223
--from-file=$COROOT/conf/apiserver/pgo.csvload-template.json \
2324
--from-file=$COROOT/conf/apiserver/pgo.lspvc-template.json

pgo/cmd/auth.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package cmd
2+
3+
/*
4+
Copyright 2017 Crunchy Data Solutions, Inc.
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
import (
19+
log "github.com/Sirupsen/logrus"
20+
"io/ioutil"
21+
"net/http"
22+
"os"
23+
"runtime"
24+
"strings"
25+
)
26+
27+
const etcpath = "/etc/pgo/pgouser"
28+
const pgouserenvvar = "PGOUSER"
29+
30+
// BasicAuthUsername and BasicAuthPassword are for BasicAuth, they are fetched from a file
31+
var BasicAuthUsername, BasicAuthPassword string
32+
33+
// StatusCheck ...
34+
func StatusCheck(resp *http.Response) {
35+
log.Debugf("http status code is %d\n", resp.StatusCode)
36+
if resp.StatusCode == 401 {
37+
log.Fatalf("Authentication Failed: %d\n", resp.StatusCode)
38+
os.Exit(2)
39+
} else if resp.StatusCode != 200 {
40+
log.Fatalf("Invalid Status Code: %d\n", resp.StatusCode)
41+
os.Exit(2)
42+
}
43+
}
44+
45+
func UserHomeDir() string {
46+
env := "HOME"
47+
if runtime.GOOS == "windows" {
48+
env = "USERPROFILE"
49+
} else if runtime.GOOS == "plan9" {
50+
env = "home"
51+
}
52+
return os.Getenv(env)
53+
}
54+
55+
func parseCredentials(dat string) (string, string) {
56+
57+
fields := strings.Split(strings.TrimSpace(dat), ":")
58+
log.Debugf("%v\n", fields)
59+
log.Debugf("username=[%s] password=[%s]\n", fields[0], fields[1])
60+
return fields[0], fields[1]
61+
}
62+
63+
func GetCredentials() {
64+
log.Debug("GetCredentials called")
65+
66+
dir := UserHomeDir()
67+
fullPath := dir + "/" + ".pgouser"
68+
log.Debug("looking in " + fullPath + " for credentials")
69+
dat, err := ioutil.ReadFile(fullPath)
70+
if err != nil {
71+
log.Debug(fullPath + " not found")
72+
} else {
73+
log.Debug(fullPath + " found")
74+
log.Debug("pgouser file found at " + fullPath + "contains " + string(dat))
75+
BasicAuthUsername, BasicAuthPassword = parseCredentials(string(dat))
76+
return
77+
}
78+
79+
fullPath = etcpath
80+
dat, err = ioutil.ReadFile(fullPath)
81+
if err != nil {
82+
log.Debug(etcpath + " not found")
83+
} else {
84+
log.Debug(fullPath + " found")
85+
log.Debug("pgouser file found at " + fullPath + "contains " + string(dat))
86+
BasicAuthUsername, BasicAuthPassword = parseCredentials(string(dat))
87+
return
88+
}
89+
90+
pgoUser := os.Getenv(pgouserenvvar)
91+
if pgoUser == "" {
92+
log.Error(pgouserenvvar + " env var not set")
93+
os.Exit(2)
94+
}
95+
96+
fullPath = pgoUser
97+
log.Debug(pgouserenvvar + " env var is being used at " + fullPath)
98+
dat, err = ioutil.ReadFile(fullPath)
99+
if err != nil {
100+
log.Error(fullPath + " file not found")
101+
os.Exit(2)
102+
}
103+
104+
log.Debug("pgouser file found at " + fullPath + "contains " + string(dat))
105+
BasicAuthUsername, BasicAuthPassword = parseCredentials(string(dat))
106+
}

pgo/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,5 @@ func initConfig() {
9494
}
9595
}
9696
log.Debug("in initConfig with url=" + APIServerURL)
97+
GetCredentials()
9798
}

pgo/cmd/version.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ func init() {
4747

4848
func showVersion() {
4949

50-
url := APIServerURL + "/version"
50+
//url := APIServerURL + "/version"
51+
url := APIServerURL + "/authtest"
5152
log.Debug(url)
5253

5354
req, err := http.NewRequest("GET", url, nil)
@@ -57,6 +58,7 @@ func showVersion() {
5758
}
5859

5960
req.Header.Set("Content-Type", "application/json")
61+
req.SetBasicAuth(BasicAuthUsername, BasicAuthPassword)
6062

6163
client := &http.Client{}
6264

@@ -69,6 +71,8 @@ func showVersion() {
6971

7072
defer resp.Body.Close()
7173

74+
StatusCheck(resp)
75+
7276
var response msgs.VersionResponse
7377

7478
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {

0 commit comments

Comments
 (0)