Skip to content

Commit

Permalink
Merge pull request #53 from rgooch/merge-bootstrap-otp
Browse files Browse the repository at this point in the history
Add unittests for admin handlers.
  • Loading branch information
cviecco committed Apr 3, 2020
2 parents 1e77b7c + cd7984c commit 7b4fc9b
Show file tree
Hide file tree
Showing 26 changed files with 473 additions and 67 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
*.key
*.pem
config.yml
4 changes: 2 additions & 2 deletions cmd/keymasterd/2fa_okta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func setupTestOtkaServer() {
}

func TestOkta2FAuthHandlerSuccess(t *testing.T) {
state, passwdFile, err := setupValidRuntimeStateSigner()
state, passwdFile, err := setupValidRuntimeStateSigner(t)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -209,7 +209,7 @@ func TestOkta2FAuthHandlerSuccess(t *testing.T) {
}

func TestOkta2FAPushStartAndWait(t *testing.T) {
state, passwdFile, err := setupValidRuntimeStateSigner()
state, passwdFile, err := setupValidRuntimeStateSigner(t)
if err != nil {
t.Fatal(err)
}
Expand Down
10 changes: 5 additions & 5 deletions cmd/keymasterd/2fa_totp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
)

func TestEncryptDecryptSuccess(t *testing.T) {
state, passwdFile, err := setupValidRuntimeStateSigner()
state, passwdFile, err := setupValidRuntimeStateSigner(t)
if err != nil {
t.Fatal(err)
}
Expand All @@ -39,7 +39,7 @@ func TestEncryptDecryptSuccess(t *testing.T) {
}

func TestGenerateNewTOTPSuccess(t *testing.T) {
state, passwdFile, err := setupValidRuntimeStateSigner()
state, passwdFile, err := setupValidRuntimeStateSigner(t)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -170,7 +170,7 @@ func setupTestStateWithTOTPSecret(t *testing.T, state *RuntimeState, cookieAuth
}

func TestVerifyTOTPHandlerSuccess(t *testing.T) {
state, passwdFile, err := setupValidRuntimeStateSigner()
state, passwdFile, err := setupValidRuntimeStateSigner(t)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -216,7 +216,7 @@ func TestVerifyTOTPHandlerSuccess(t *testing.T) {
}

func TestAuthTOTPHandlerSuccess(t *testing.T) {
state, passwdFile, err := setupValidRuntimeStateSigner()
state, passwdFile, err := setupValidRuntimeStateSigner(t)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -262,7 +262,7 @@ func TestAuthTOTPHandlerSuccess(t *testing.T) {
}

func TestTOTPTokenManagerHandlerUpdateSuccess(t *testing.T) {
state, passwdFile, err := setupValidRuntimeStateSigner()
state, passwdFile, err := setupValidRuntimeStateSigner(t)
if err != nil {
t.Fatal(err)
}
Expand Down
20 changes: 10 additions & 10 deletions cmd/keymasterd/adminHandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ func (state *RuntimeState) sendFailureToClientIfNonAdmin(w http.ResponseWriter,
if state.sendFailureToClientIfLocked(w, r) {
return true, ""
}
// TODO: probably this should be just u2f and authkeymaterx509... but
// TODO: probably this should be just u2f and AuthTypeKeymasterX509... but
// probably we want also to allow configurability for this. Leaving
// AuthTypeKeymasterX509 as optional for now
authUser, _, err := state.checkAuth(w, r,
state.getRequiredWebUIAuthLevel()|AuthTypeKeymasterX509)
if err != nil {
logger.Debugf(1, "%v", err)
state.logger.Debugf(1, "%v", err)
return true, ""
}
w.(*instrumentedwriter.LoggingWriter).SetUsername(authUser)
Expand All @@ -50,7 +50,7 @@ func (state *RuntimeState) ensurePostAndGetUsername(w http.ResponseWriter,
}
err := r.ParseForm()
if err != nil {
logger.Printf("error parsing err=%s", err)
state.logger.Printf("error parsing err=%s", err)
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
return ""
}
Expand All @@ -68,7 +68,7 @@ func (state *RuntimeState) ensurePostAndGetUsername(w http.ResponseWriter,
username := formUsername[0]
matched, err := regexp.Match(`^[A-Za-z0-9-_.]+$`, []byte(username))
if err != nil {
logger.Printf("error parsing err=%s", err)
state.logger.Printf("error parsing err=%s", err)
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
return ""
}
Expand All @@ -82,15 +82,15 @@ func (state *RuntimeState) ensurePostAndGetUsername(w http.ResponseWriter,

func (state *RuntimeState) usersHandler(w http.ResponseWriter,
r *http.Request) {
logger.Debugf(3, "Top of usersHandler r=%+v", r)
state.logger.Debugf(3, "Top of usersHandler r=%+v", r)
failure, authUser := state.sendFailureToClientIfNonAdmin(w, r)
if failure || authUser == "" {
return
}
w.(*instrumentedwriter.LoggingWriter).SetUsername(authUser)
users, _, err := state.GetUsers()
if err != nil {
logger.Printf("Getting users error: %v", err)
state.logger.Printf("Getting users error: %v", err)
http.Error(w, "error", http.StatusInternalServerError)
return
}
Expand All @@ -102,7 +102,7 @@ func (state *RuntimeState) usersHandler(w http.ResponseWriter,
JSSources: JSSources}
err = state.htmlTemplate.ExecuteTemplate(w, "usersPage", displayData)
if err != nil {
logger.Printf("Failed to execute %v", err)
state.logger.Printf("Failed to execute %v", err)
http.Error(w, "error", http.StatusInternalServerError)
return
}
Expand All @@ -120,7 +120,7 @@ func (state *RuntimeState) addUserHandler(w http.ResponseWriter,
// Check if username already exists.
profile, existing, fromCache, err := state.LoadUserProfile(username)
if err != nil {
logger.Printf("error parsing err=%s", err)
state.logger.Printf("error parsing err=%s", err)
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
return
}
Expand All @@ -135,7 +135,7 @@ func (state *RuntimeState) addUserHandler(w http.ResponseWriter,
return
}
if err := state.SaveUserProfile(username, profile); err != nil {
logger.Printf("error Savinf Profile err=%s", err)
state.logger.Printf("error Savinf Profile err=%s", err)
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
return
}
Expand All @@ -160,7 +160,7 @@ func (state *RuntimeState) deleteUserHandler(w http.ResponseWriter,
return
}
if err := state.DeleteUserProfile(username); err != nil {
logger.Printf("error parsing err=%s", err)
state.logger.Printf("error parsing err=%s", err)
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
return
}
Expand Down
177 changes: 177 additions & 0 deletions cmd/keymasterd/adminHandlers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package main

import (
// "bytes"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
// "fmt"
// "io"
"io/ioutil"
"net/http"
"net/http/httptest"
// "net/url"
"os"
// "strconv"
// "strings"
"testing"

// "github.com/Cloud-Foundations/keymaster/keymasterd/eventnotifier"
"github.com/Cloud-Foundations/keymaster/lib/instrumentedwriter"
// "github.com/Cloud-Foundations/keymaster/lib/webapi/v0/proto"
)

func testCreateRuntimeStateWithBothCAs(t *testing.T) (
*RuntimeState, string, error) {
state, tmpdir, err := newTestingState(t)
if err != nil {
return nil, "", err
}
doCleanup := true
defer func() {
if doCleanup {
os.RemoveAll(tmpdir)
}
}()
state.Config.Base.AdminUsers = []string{"alice"}
adminCaPemData, err := ioutil.ReadFile("testdata/AdminCA.pem")
if err != nil {
return nil, "", err
}
state.ClientCAPool = x509.NewCertPool()
if !state.ClientCAPool.AppendCertsFromPEM(adminCaPemData) {
return nil, "", errors.New("cannot append Admin CA certificate")
}
keymasterCaKeyData, err := ioutil.ReadFile("testdata/KeymasterCA.key")
if err != nil {
return nil, "", err
}
signer, err := getSignerFromPEMBytes(keymasterCaKeyData)
if err != nil {
return nil, "", err
}
state.Signer = signer
state.caCertDer, err = generateCADer(state, state.Signer)
if err != nil {
return nil, "", err
}
state.signerPublicKeyToKeymasterKeys()
state.totpLocalRateLimit = make(map[string]totpRateLimitInfo)
if err := initDB(state); err != nil {
t.Fatal(err)
}
doCleanup = false
return state, tmpdir, nil
}

func testMakeConnectionState(certs ...string) (*tls.ConnectionState, error) {
var chain []*x509.Certificate
for _, cert := range certs {
pemData, err := ioutil.ReadFile(cert)
if err != nil {
return nil, err
}
block, _ := pem.Decode(pemData)
if block.Type != "CERTIFICATE" {
return nil, errors.New("no CERTIFICATE found")
}
x509Cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
chain = append(chain, x509Cert)
}
return &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{chain}},
nil
}

func TestAuthNoTLS(t *testing.T) {
state, tmpdir, err := testCreateRuntimeStateWithBothCAs(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
recorder := httptest.NewRecorder()
w := &instrumentedwriter.LoggingWriter{ResponseWriter: recorder}
req := httptest.NewRequest("GET", usersPath, nil)
errorSent, username := state.sendFailureToClientIfNonAdmin(w, req)
if errorSent {
return
}
if recorder.Result().StatusCode != http.StatusUnauthorized {
t.Errorf("unexpected status code: %d", recorder.Result().StatusCode)
}
if username != "" {
t.Errorf("expected no username, got: %s", username)
}
}

func TestAuthCertAdminUser(t *testing.T) {
state, tmpdir, err := testCreateRuntimeStateWithBothCAs(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
recorder := httptest.NewRecorder()
w := &instrumentedwriter.LoggingWriter{ResponseWriter: recorder}
req := httptest.NewRequest("GET", usersPath, nil)
req.TLS, err = testMakeConnectionState("testdata/alice.pem",
"testdata/KeymasterCA.pem")
errorSent, username := state.sendFailureToClientIfNonAdmin(w, req)
if errorSent {
t.Fatal("error was sent")
}
if recorder.Result().StatusCode != http.StatusOK {
t.Fatalf("unexpected status code: %d", recorder.Result().StatusCode)
}
if username != "alice" {
t.Fatalf("unexpected username: alice, got: %s", username)
}
}

func TestAuthCertPlainUser(t *testing.T) {
state, tmpdir, err := testCreateRuntimeStateWithBothCAs(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
recorder := httptest.NewRecorder()
w := &instrumentedwriter.LoggingWriter{ResponseWriter: recorder}
req := httptest.NewRequest("GET", usersPath, nil)
req.TLS, err = testMakeConnectionState("testdata/bob.pem",
"testdata/KeymasterCA.pem")
errorSent, username := state.sendFailureToClientIfNonAdmin(w, req)
if !errorSent {
t.Error("no error was sent")
}
if recorder.Result().StatusCode != http.StatusUnauthorized {
t.Errorf("unexpected status code: %d", recorder.Result().StatusCode)
}
if username != "" {
t.Errorf("expected no username, got: %s", username)
}
}

func TestAuthCertFakeAdminUser(t *testing.T) {
state, tmpdir, err := testCreateRuntimeStateWithBothCAs(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
recorder := httptest.NewRecorder()
w := &instrumentedwriter.LoggingWriter{ResponseWriter: recorder}
req := httptest.NewRequest("GET", usersPath, nil)
req.TLS, err = testMakeConnectionState("testdata/alice-fake.pem",
"testdata/AdminCA.pem")
errorSent, username := state.sendFailureToClientIfNonAdmin(w, req)
if !errorSent {
t.Error("no error was sent")
}
if recorder.Result().StatusCode != http.StatusUnauthorized {
t.Errorf("unexpected status code: %d", recorder.Result().StatusCode)
}
if username != "" {
t.Errorf("expected no username, got: %s", username)
}
}

0 comments on commit 7b4fc9b

Please sign in to comment.