Files
ziti/tests/api_session_totp_token_test.go
Andrew Martinez 948735d86c fixes openziti/ziti#4094 accept first-party certs issued by a separat… (#4140)
* fixes openziti/ziti#4094 accept first-party certs issued by a separate edge signing CA

- adds FirstPartyX509CertValidation and ThirdPartyX509CertValidation usages and an
  intermediates field to the router data model public keys, deprecating
  ClientX509CertValidation
- publishes config CA bundle roots as first-party anchors with their intermediates and
  Ca store entries as third-party anchors; controller certs carry JWT validation only,
  since a controller identity is never a CA and anchors no client cert chains
- builds router first-party and client cert trust pools from the published usages,
  falling back to the deprecated usage against older controllers
- propagates the full signing cert chain between controllers via a new mesh
  SigningCertChainHeader and persists whole chains in controller records
- removes the orphaned InstantStrategy.AddPublicKey, dead since public key sync moved
  to controller list data
- gives each command dispatcher its own decoder registry so multiple in-process
  controllers no longer decode into the last-started controller's managers
- adds a three-controller in-process HA test harness with a split signing PKI,
  cluster formation and first-party cert integration tests
- trusts the edge signing CA when verifying router control channel certs
- adds a variadic additionalRoots parameter to VerifyLeafCertChain, applied to a
  clone of the caller's pool so an identity's live tls.Configs are unaffected
- passes the edge enrollment signing CA bundle as additional roots when admitting
  a router control channel connection, so a deployment whose signing CA sits
  outside the controller's own trust bundle no longer has every router refused
- leaves the fingerprint check bound to the verified leaf, so the wider anchor set
  changes which chains verify, not which routers are admitted
- covers the split-root case and the caller-pool guarantee in common/cert tests
2026-08-26 14:07:31 -04:00

206 lines
6.5 KiB
Go

package tests
import (
"testing"
"time"
"github.com/golang-jwt/jwt/v5"
edge_apis "github.com/openziti/sdk-golang/v2/edge-apis"
"github.com/openziti/ziti/v2/common"
)
func Test_API_Session_TOTP_Tokens(t *testing.T) {
ctx := NewTestContext(t)
defer ctx.Teardown()
ctx.StartServer()
managementApi := ctx.NewEdgeManagementApi(nil)
adminCreds := ctx.NewAdminCredentials()
apiSession, err := managementApi.Authenticate(adminCreds, nil)
ctx.Req.NoError(err)
ctx.Req.NotNil(apiSession)
t.Run("non admins in the client api", func(t *testing.T) {
ctx.testContextChanged(t)
nonAdminIdentity, nonAdminCreds, err := managementApi.CreateAndEnrollOttIdentity(false)
ctx.Req.NoError(err)
ctx.Req.NotNil(nonAdminIdentity)
ctx.Req.NotNil(nonAdminCreds)
nonAdminClientClient := ctx.NewEdgeClientApi(nil)
nonAdminApiSession, err := nonAdminClientClient.Authenticate(nonAdminCreds, nil)
ctx.Req.NoError(err)
ctx.Req.NotNil(nonAdminApiSession)
nonAdminTotpProvider, nonAdminTotpDetail, err := nonAdminClientClient.EnrollTotpMfa()
ctx.Req.NoError(err)
ctx.Req.NotNil(nonAdminTotpDetail)
ctx.Req.NotNil(nonAdminTotpProvider)
t.Run("cannot create totp tokens using legacy authentication", func(t *testing.T) {
ctx.testContextChanged(t)
nonAdminClientClient = ctx.NewEdgeClientApi(nonAdminTotpProvider.FuncProvider())
nonAdminClientClient.SetUseOidc(false)
nonAdminClientClient.SetAllowOidcDynamicallyEnabled(false)
nonAdminApiSession, err = nonAdminClientClient.Authenticate(nonAdminCreds, nil)
ctx.Req.NoError(err)
ctx.Req.NotNil(nonAdminApiSession)
ctx.Req.Equal(edge_apis.ApiSessionTypeLegacy, nonAdminApiSession.GetType())
code := nonAdminTotpProvider.Code()
token, err := nonAdminClientClient.GetTotpToken(code)
ctx.Req.Error(err)
ctx.Req.Empty(token)
})
t.Run("can create totp tokens using oidc authentication", func(t *testing.T) {
ctx.testContextChanged(t)
nonAdminClientClient = ctx.NewEdgeClientApi(nonAdminTotpProvider.FuncProvider())
nonAdminClientClient.SetUseOidc(true)
nonAdminClientClient.SetAllowOidcDynamicallyEnabled(true)
nonAdminApiSession, err = nonAdminClientClient.Authenticate(nonAdminCreds, nil)
ctx.Req.NoError(err)
ctx.Req.NotNil(nonAdminApiSession)
ctx.Req.Equal(edge_apis.ApiSessionTypeOidc, nonAdminApiSession.GetType())
code := nonAdminTotpProvider.Code()
totpToken, err := nonAdminClientClient.GetTotpToken(code)
ctx.Req.NoError(err)
ctx.Req.NotNil(totpToken)
ctx.Req.NotNil(totpToken.Token)
t.Run("the token is a JWT and has the correct values", func(t *testing.T) {
ctx.testContextChanged(t)
totpClaims := &common.TotpClaims{}
totpJwtToken, err := jwt.ParseWithClaims(*totpToken.Token, totpClaims, ctx.EdgeController.AppEnv.JwtSignerKeyFunc)
ctx.NoError(err)
ctx.NotNil(totpJwtToken)
accessClaims := &common.AccessClaims{}
apiSessionJwtToken, err := jwt.ParseWithClaims(string(nonAdminApiSession.GetToken()), accessClaims, ctx.EdgeController.AppEnv.JwtSignerKeyFunc)
ctx.NoError(err)
ctx.NotNil(apiSessionJwtToken)
ctx.Req.NoError(err)
ctx.NotNil(totpJwtToken)
ctx.Equal(common.TokenTypeTotp, totpClaims.Type)
expTime, err := totpJwtToken.Claims.GetExpirationTime()
ctx.NoError(err)
ctx.Nil(expTime)
ctx.Equal(accessClaims.ApiSessionId, totpClaims.ApiSessionId)
})
})
})
t.Run("admins in the management api", func(t *testing.T) {
ctx.testContextChanged(t)
adminIdentity, adminCreds, err := managementApi.CreateAndEnrollOttIdentity(true)
ctx.Req.NoError(err)
ctx.Req.NotNil(adminIdentity)
ctx.Req.NotNil(adminCreds)
adminManagementClient := ctx.NewEdgeManagementApi(nil)
adminApiSession, err := adminManagementClient.Authenticate(adminCreds, nil)
ctx.Req.NoError(err)
ctx.Req.NotNil(adminApiSession)
adminTotpProvider, adminTotpDetail, err := adminManagementClient.EnrollTotpMfa()
ctx.Req.NoError(err)
ctx.Req.NotNil(adminTotpDetail)
ctx.Req.NotNil(adminTotpProvider)
t.Run("cannot create totp tokens using legacy authentication", func(t *testing.T) {
ctx.testContextChanged(t)
adminManagementClient = ctx.NewEdgeManagementApi(adminTotpProvider.FuncProvider())
adminManagementClient.SetUseOidc(false)
adminManagementClient.SetAllowOidcDynamicallyEnabled(false)
adminApiSession, err = adminManagementClient.Authenticate(adminCreds, nil)
ctx.Req.NoError(err)
ctx.Req.NotNil(adminApiSession)
ctx.Req.Equal(edge_apis.ApiSessionTypeLegacy, adminApiSession.GetType())
code := adminTotpProvider.Code()
token, err := adminManagementClient.GetTotpToken(code)
ctx.Req.Error(err)
ctx.Req.Empty(token)
})
t.Run("can create totp tokens using oidc authentication", func(t *testing.T) {
ctx.testContextChanged(t)
adminManagementClient = ctx.NewEdgeManagementApi(adminTotpProvider.FuncProvider())
adminManagementClient.SetUseOidc(true)
adminManagementClient.SetAllowOidcDynamicallyEnabled(true)
adminApiSession, err = adminManagementClient.Authenticate(adminCreds, nil)
ctx.Req.NoError(err)
ctx.Req.NotNil(adminApiSession)
ctx.Req.Equal(edge_apis.ApiSessionTypeOidc, adminApiSession.GetType())
code := adminTotpProvider.Code()
beforeTotpCreate := time.Now()
totpToken, err := adminManagementClient.GetTotpToken(code)
afterTotpCreate := time.Now()
ctx.Req.NoError(err)
ctx.Req.NotNil(totpToken)
ctx.Req.NotNil(totpToken.Token)
t.Run("the token is a JWT and has the correct values", func(t *testing.T) {
ctx.testContextChanged(t)
totpClaims := &common.TotpClaims{}
totpJwtToken, err := jwt.ParseWithClaims(*totpToken.Token, totpClaims, ctx.EdgeController.AppEnv.JwtSignerKeyFunc)
ctx.NoError(err)
ctx.NotNil(totpJwtToken)
accessClaims := &common.AccessClaims{}
apiSessionJwtToken, err := jwt.ParseWithClaims(string(adminApiSession.GetToken()), accessClaims, ctx.EdgeController.AppEnv.JwtSignerKeyFunc)
ctx.NoError(err)
ctx.NotNil(apiSessionJwtToken)
ctx.Req.NoError(err)
ctx.NotNil(totpJwtToken)
ctx.Equal(common.TokenTypeTotp, totpClaims.Type)
expTime, err := totpJwtToken.Claims.GetExpirationTime()
ctx.NoError(err)
ctx.Nil(expTime)
issuedAt, err := totpJwtToken.Claims.GetIssuedAt()
ctx.NoError(err)
ctx.NotNil(issuedAt)
// The serialized iat truncates to whole seconds, so the lower bound must too.
ctx.Req.WithinRange(issuedAt.Time, beforeTotpCreate.Truncate(time.Second), afterTotpCreate)
ctx.Equal(accessClaims.ApiSessionId, totpClaims.ApiSessionId)
})
})
})
}