Skip to content

Commit

Permalink
Merge branch 'master' into add-sshcert-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
cviecco committed Oct 17, 2024
2 parents 32c3d09 + 35a5f3d commit 873147d
Show file tree
Hide file tree
Showing 18 changed files with 189 additions and 146 deletions.
1 change: 1 addition & 0 deletions cmd/keymaster/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ func setupCerts(

}
}
logger.Debugf(1, "SetupCerts: authentication Complete")
if err := signers.Wait(); err != nil {
return err
}
Expand Down
12 changes: 12 additions & 0 deletions cmd/keymasterd/2fa_okta.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,24 @@ func (state *RuntimeState) oktaPushStartHandler(w http.ResponseWriter, r *http.R
state.writeFailureResponse(w, r, http.StatusInternalServerError, "Apperent Misconfiguration")
return
}
userResponse, err := oktaAuth.GetValidUserResponse(authData.Username)
if err != nil {
logger.Debugf(2, "oktaPushStartHandler: ")
}
if len(userResponse.Embedded.Factor) < 1 {
logger.Printf("oktaPushStartHandler: user %s does not have valid authenticators", authData.Username)
logger.Debugf(2, "oktaPushStartHandler: userdata for broken user%s is :%s", authData.Username, userResponse)
state.writeFailureResponse(w, r, http.StatusPreconditionFailed, "No valid MFA authenticators available")
return
}

pushResponse, err := oktaAuth.ValidateUserPush(authData.Username)
if err != nil {
logger.Println(err)
state.writeFailureResponse(w, r, http.StatusInternalServerError, "Failure when validating OKTA push")
return
}
logger.Debugf(2, "oktaPushStartHandler: after validating push response=%+v", pushResponse)
switch pushResponse {
case okta.PushResponseWaiting:
w.WriteHeader(http.StatusOK)
Expand Down
3 changes: 2 additions & 1 deletion cmd/keymasterd/adminHandlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ func testCreateRuntimeStateWithBothCAs(t *testing.T) (
return nil, "", err
}
state.Signer = signer
state.caCertDer, err = generateCADer(state, state.Signer)
caCertDer, err := generateCADer(state, state.Signer)
if err != nil {
return nil, "", err
}
state.caCertDer = append(state.caCertDer, caCertDer)
state.signerPublicKeyToKeymasterKeys()
state.totpLocalRateLimit = make(map[string]totpRateLimitInfo)
if err := initDB(state); err != nil {
Expand Down
56 changes: 48 additions & 8 deletions cmd/keymasterd/app.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bytes"
"crypto"
"crypto/rand"
"crypto/tls"
Expand Down Expand Up @@ -30,6 +31,7 @@ import (
texttemplate "text/template"
"time"

"golang.org/x/crypto/ssh"
"golang.org/x/net/context"
"golang.org/x/time/rate"

Expand Down Expand Up @@ -195,7 +197,7 @@ type RuntimeState struct {
ClientCAPool *x509.CertPool
HostIdentity string
KerberosRealm *string
caCertDer []byte
caCertDer [][]byte
certManager *certmanager.CertificateManager
vipPushCookie map[string]pushPollTransaction
localAuthData map[string]localUserData
Expand Down Expand Up @@ -1043,18 +1045,54 @@ func (state *RuntimeState) publicPathHandler(w http.ResponseWriter, r *http.Requ

target := r.URL.Path[len(publicPath):]

caPubMaxSeconds := 30
switch target {
case "loginForm":
//fmt.Fprintf(w, "%s", loginFormText)
setSecurityHeaders(w)
state.writeHTMLLoginPage(w, r, 200, "", profilePath, "")
return
case "x509ca":
pemCert := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: state.caCertDer}))
var outCABuf bytes.Buffer
for _, derCert := range state.caCertDer {
err := pem.Encode(&outCABuf, &pem.Block{Type: "CERTIFICATE", Bytes: derCert})
if err != nil {
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
logger.Printf("Error computing pemCA")
return
}
}
w.Header().Add("Cache-Control",
fmt.Sprintf("max-age=%d, public, must-revalidate, proxy-revalidate",
caPubMaxSeconds))
w.Header().Set("Content-Disposition", `attachment; filename=keymasterx509CA.pem"`)
w.WriteHeader(200)
outCABuf.WriteTo(w)
case "sshca":
var outCABuf bytes.Buffer
for _, pub := range state.KeymasterPublicKeys {
sshPub, err := ssh.NewPublicKey(pub)
if err != nil {
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
logger.Printf("Error computing sshCA")
return
}
pubBytes := ssh.MarshalAuthorizedKey(sshPub)
_, err = fmt.Fprintf(&outCABuf, "%s", pubBytes)
if err != nil {
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
logger.Printf("Error computing sshCA")
return
}

w.Header().Set("Content-Disposition", `attachment; filename="id_rsa-cert.pub"`)
}
w.Header().Add("Cache-Control",
fmt.Sprintf("max-age=%d, public, must-revalidate, proxy-revalidate",
caPubMaxSeconds))
w.Header().Set("Content-Disposition", `attachment; filename=keymastersshCCA.pub"`)
w.WriteHeader(200)
fmt.Fprintf(w, "%s", pemCert)
outCABuf.WriteTo(w)

default:
state.writeFailureResponse(w, r, http.StatusNotFound, "")
return
Expand Down Expand Up @@ -1985,11 +2023,13 @@ func startServerAfterLoad(runtimeState *RuntimeState, realLogger *serverlogger.L
if runtimeState.ClientCAPool == nil {
runtimeState.ClientCAPool = x509.NewCertPool()
}
myCert, err := x509.ParseCertificate(runtimeState.caCertDer)
if err != nil {
panic(err)
for _, derCert := range runtimeState.caCertDer {
myCert, err := x509.ParseCertificate(derCert)
if err != nil {
panic(err)
}
runtimeState.ClientCAPool.AddCert(myCert)
}
runtimeState.ClientCAPool.AddCert(myCert)
// Safari in MacOS 10.12.x required a cert to be presented by the user even
// when optional.
// Our usage shows this is less than 1% of users so we are now mandating
Expand Down
7 changes: 4 additions & 3 deletions cmd/keymasterd/authToken.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (

"github.com/Cloud-Foundations/keymaster/lib/instrumentedwriter"
"github.com/Cloud-Foundations/keymaster/lib/paths"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"

"github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
)

func (state *RuntimeState) generateAuthJWT(username string) (string, error) {
Expand All @@ -32,7 +33,7 @@ func (state *RuntimeState) generateAuthJWT(username string) (string, error) {
IssuedAt: now,
TokenType: "keymaster_webauth_for_cli_identity",
}
return jwt.Signed(signer).Claims(authToken).CompactSerialize()
return jwt.Signed(signer).Claims(authToken).Serialize()
}

func (state *RuntimeState) SendAuthDocumentHandler(w http.ResponseWriter,
Expand Down
8 changes: 6 additions & 2 deletions cmd/keymasterd/awsRole.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,16 @@ func (state *RuntimeState) generateRoleCert(template *x509.Certificate,
if !strong {
return nil, fmt.Errorf("key too weak")
}
caCert, err := x509.ParseCertificate(state.caCertDer)
signer, caCertDer, err := state.getSignerX509CAForPublic(publicKey)
if err != nil {
return nil, err
}
caCert, err := x509.ParseCertificate(caCertDer)
if err != nil {
return nil, err
}
certDER, err := x509.CreateCertificate(rand.Reader, template, caCert,
publicKey, state.Signer)
publicKey, signer)
if err != nil {
return nil, err
}
Expand Down
17 changes: 15 additions & 2 deletions cmd/keymasterd/certgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,13 @@ func (state *RuntimeState) expandSSHExtensions(username string) (map[string]stri
return userExtensions, nil
}

func (state *RuntimeState) getSignerX509CAForPublic(pub interface{}) (crypto.Signer, []byte, error) {
//v0... always returnt the primary signer
baseIndex := len(state.caCertDer) - 1
return state.Signer, state.caCertDer[baseIndex], nil

}

func (state *RuntimeState) postAuthSSHCertHandler(
w http.ResponseWriter, r *http.Request, targetUser string,
duration time.Duration) {
Expand Down Expand Up @@ -453,14 +460,20 @@ func (state *RuntimeState) postAuthX509CertHandler(
logger.Printf("Invalid File, Check Key strength/key type")
return
}
caCert, err := x509.ParseCertificate(state.caCertDer)
signer, caCertDer, err := state.getSignerX509CAForPublic(userPub)
if err != nil {
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
logger.Printf("Error Finding Cert for public key: %s\n data", err)
return
}
caCert, err := x509.ParseCertificate(caCertDer)
if err != nil {
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
logger.Printf("Cannot parse CA Der: %s\n data", err)
return
}
derCert, err := certgen.GenUserX509Cert(targetUser, userPub, caCert,
keySigner, state.KerberosRealm, duration, groups, organizations,
signer, state.KerberosRealm, duration, groups, organizations,
serviceMethods, logger)
if err != nil {
state.writeFailureResponse(w, r, http.StatusInternalServerError, "")
Expand Down
41 changes: 28 additions & 13 deletions cmd/keymasterd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bufio"
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
Expand Down Expand Up @@ -262,23 +263,30 @@ func (state *RuntimeState) loadTemplates() (err error) {
func (state *RuntimeState) signerPublicKeyToKeymasterKeys() error {
state.logger.Debugf(3, "number of pk known=%d",
len(state.KeymasterPublicKeys))
signerPKFingerprint, err := getKeyFingerprint(state.Signer.Public())
if err != nil {
return err
var localSigners []crypto.Signer
if state.Ed25519Signer != nil {
localSigners = append(localSigners, state.Ed25519Signer)
}
found := false
for _, key := range state.KeymasterPublicKeys {
fp, err := getKeyFingerprint(key)
localSigners = append(localSigners, state.Signer)
for _, signer := range localSigners {
signerPKFingerprint, err := getKeyFingerprint(signer.Public())
if err != nil {
return err
}
if signerPKFingerprint == fp {
found = true
found := false
for _, key := range state.KeymasterPublicKeys {
fp, err := getKeyFingerprint(key)
if err != nil {
return err
}
if signerPKFingerprint == fp {
found = true
}
}
if !found {
state.KeymasterPublicKeys = append(state.KeymasterPublicKeys,
signer.Public())
}
}
if !found {
state.KeymasterPublicKeys = append(state.KeymasterPublicKeys,
state.Signer.Public())
}
state.logger.Debugf(3, "number of pk known=%d",
len(state.KeymasterPublicKeys))
Expand Down Expand Up @@ -312,6 +320,12 @@ func (state *RuntimeState) loadSignersFromPemData(signerPem, ed25519Pem []byte)
default:
return fmt.Errorf("Ed2559 configred file is not really an Ed25519 key. Type is %T!\n", v)
}
ed25519CaCertDer, err := generateCADer(state, edSigner)
if err != nil {
state.logger.Printf("Cannot generate Ed25519 CA DER")
return err
}
state.caCertDer = append(state.caCertDer, ed25519CaCertDer)
state.Ed25519Signer = edSigner
}
signer, err := getSignerFromPEMBytes(signerPem)
Expand All @@ -327,11 +341,12 @@ func (state *RuntimeState) loadSignersFromPemData(signerPem, ed25519Pem []byte)
default:
return fmt.Errorf("Signer file is a valid Signer key. Type is %T!\n", v)
}
state.caCertDer, err = generateCADer(state, signer)
caCertDer, err := generateCADer(state, signer)
if err != nil {
state.logger.Printf("Cannot generate CA DER")
return err
}
state.caCertDer = append(state.caCertDer, caCertDer)
// Assignment of signer MUST be the last operation after
// all error checks
state.Signer = signer
Expand Down
14 changes: 7 additions & 7 deletions cmd/keymasterd/idp_oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import (

"github.com/Cloud-Foundations/keymaster/lib/authutil"
"github.com/Cloud-Foundations/keymaster/lib/instrumentedwriter"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
"github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
)

//For minimal openid connect interaface and easy config we need 5 enpoints
Expand Down Expand Up @@ -491,7 +491,7 @@ func (state *RuntimeState) idpOpenIDCAuthorizationHandler(w http.ResponseWriter,
}
logger.Debugf(3, "auth request is valid, now proceeding to generate redirect")

raw, err := jwt.Signed(signer).Claims(codeToken).CompactSerialize()
raw, err := jwt.Signed(signer).Claims(codeToken).Serialize()
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -592,7 +592,7 @@ func (state *RuntimeState) idpOpenIDCTokenHandler(w http.ResponseWriter, r *http
return

}
tok, err := jwt.ParseSigned(codeString)
tok, err := jwt.ParseSigned(codeString, []jose.SignatureAlgorithm{jose.RS256})
if err != nil {
logger.Printf("err=%s", err)
state.writeFailureResponse(w, r, http.StatusBadRequest, "bad code")
Expand Down Expand Up @@ -741,7 +741,7 @@ func (state *RuntimeState) idpOpenIDCTokenHandler(w http.ResponseWriter, r *http
idToken.Expiration = keymasterToken.AuthExpiration
idToken.IssuedAt = time.Now().Unix()

signedIdToken, err := jwt.Signed(signer).Claims(idToken).CompactSerialize()
signedIdToken, err := jwt.Signed(signer).Claims(idToken).Serialize()
if err != nil {
log.Printf("error signing idToken in idpOpenIDCTokenHandler,: %s", err)
state.writeFailureResponse(w, r, http.StatusInternalServerError, "Internal Error")
Expand All @@ -756,7 +756,7 @@ func (state *RuntimeState) idpOpenIDCTokenHandler(w http.ResponseWriter, r *http
if len(keymasterToken.AccessAudience) > 0 {
accessToken.Audience = append(keymasterToken.AccessAudience, state.idpGetIssuer()+idpOpenIDCUserinfoPath)
}
signedAccessToken, err := jwt.Signed(signer).Claims(accessToken).CompactSerialize()
signedAccessToken, err := jwt.Signed(signer).Claims(accessToken).Serialize()
if err != nil {
log.Printf("error signing accessToken in idpOpenIDCTokenHandler: %s", err)
state.writeFailureResponse(w, r, http.StatusInternalServerError, "Internal Error")
Expand Down Expand Up @@ -927,7 +927,7 @@ func (state *RuntimeState) idpOpenIDCUserinfoHandler(w http.ResponseWriter,
"Missing access token")
return
}
tok, err := jwt.ParseSigned(accessToken)
tok, err := jwt.ParseSigned(accessToken, []jose.SignatureAlgorithm{jose.RS256})
if err != nil {
logger.Printf("err=%s", err)
state.writeFailureResponse(w, r, http.StatusBadRequest,
Expand Down
7 changes: 4 additions & 3 deletions cmd/keymasterd/idp_oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import (
"testing"

"github.com/Cloud-Foundations/Dominator/lib/log/debuglogger"
"github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
cv "github.com/nirasan/go-oauth-pkce-code-verifier"
"gopkg.in/square/go-jose.v2/jwt"
)

func init() {
Expand Down Expand Up @@ -159,7 +160,7 @@ func TestIDPOpenIDCAuthorizationHandlerSuccess(t *testing.T) {
}
rCode := location.Query().Get("code")
t.Logf("rCode=%s", rCode)
tok, err := jwt.ParseSigned(rCode)
tok, err := jwt.ParseSigned(rCode, []jose.SignatureAlgorithm{jose.RS256})
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -585,7 +586,7 @@ func TestIDPOpenIDCPKCEFlowWithAudienceSuccess(t *testing.T) {
t.Logf("resultAccessToken='%+v'", resultAccessToken)

// lets parse the access token to ensure the requested audience is there.
tok, err := jwt.ParseSigned(resultAccessToken.AccessToken)
tok, err := jwt.ParseSigned(resultAccessToken.AccessToken, []jose.SignatureAlgorithm{jose.RS256})
if err != nil {
t.Fatal(err)
}
Expand Down
Loading

0 comments on commit 873147d

Please sign in to comment.