Skip to content

Commit

Permalink
Merge pull request #2560 from openziti/fix.2559.enforce.expires.at
Browse files Browse the repository at this point in the history
fixes #2559 expires at not enforced in all enrollment modules
  • Loading branch information
andrewpmartinez authored Nov 21, 2024
2 parents 01d8122 + 94ef43b commit da3674f
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 13 deletions.
3 changes: 3 additions & 0 deletions controller/model/enrollment_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package model
import (
"crypto/x509"
"fmt"
"github.com/google/uuid"
"github.com/michaelquigley/pfxlog"
"github.com/openziti/foundation/v2/errorz"
"github.com/openziti/identity"
Expand Down Expand Up @@ -291,7 +292,9 @@ func (self *EnrollmentManager) RefreshJwt(id string, expiresAt time.Time, ctx *c
return err
}

enrollment.Token = uuid.New().String()
return self.Update(enrollment, fields.UpdatedFieldsMap{
db.FieldEnrollmentToken: struct{}{},
db.FieldEnrollmentJwt: struct{}{},
db.FieldEnrollmentExpiresAt: struct{}{},
db.FieldEnrollmentIssuedAt: struct{}{},
Expand Down
5 changes: 5 additions & 0 deletions controller/model/enrollment_mod_ott.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/openziti/ziti/controller/change"
"github.com/openziti/ziti/controller/db"
"github.com/openziti/ziti/controller/models"
"time"
)

type EnrollModuleOtt struct {
Expand Down Expand Up @@ -55,6 +56,10 @@ func (module *EnrollModuleOtt) Process(ctx EnrollmentContext) (*EnrollmentResult
return nil, apierror.NewInvalidEnrollmentToken()
}

if enrollment.ExpiresAt == nil || enrollment.ExpiresAt.IsZero() || enrollment.ExpiresAt.Before(time.Now()) {
return nil, apierror.NewEnrollmentExpired()
}

identity, err := module.env.GetManagers().Identity.Read(*enrollment.IdentityId)

if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions controller/model/enrollment_mod_ottca.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/openziti/ziti/controller/change"
"github.com/openziti/ziti/controller/db"
"github.com/openziti/ziti/controller/models"
"time"
)

type EnrollModuleOttCa struct {
Expand Down Expand Up @@ -55,6 +56,10 @@ func (module *EnrollModuleOttCa) Process(ctx EnrollmentContext) (*EnrollmentResu
return nil, apierror.NewInvalidEnrollmentToken()
}

if enrollment.ExpiresAt == nil || enrollment.ExpiresAt.IsZero() || enrollment.ExpiresAt.Before(time.Now()) {
return nil, apierror.NewEnrollmentExpired()
}

identity, err := module.env.GetManagers().Identity.Read(*enrollment.IdentityId)

if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions controller/model/enrollment_mod_updb.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/openziti/ziti/controller/change"
"github.com/openziti/ziti/controller/db"
"github.com/openziti/ziti/controller/models"
"time"
)

type EnrollModuleUpdb struct {
Expand Down Expand Up @@ -56,6 +57,10 @@ func (module *EnrollModuleUpdb) Process(ctx EnrollmentContext) (*EnrollmentResul
return nil, apierror.NewInvalidEnrollmentToken()
}

if enrollment.ExpiresAt == nil || enrollment.ExpiresAt.IsZero() || enrollment.ExpiresAt.Before(time.Now()) {
return nil, apierror.NewEnrollmentExpired()
}

identity, err := module.env.GetManagers().Identity.Read(*enrollment.IdentityId)

if err != nil {
Expand Down
239 changes: 226 additions & 13 deletions tests/enrollment_create_test.go → tests/enrollment_ott_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,23 @@
package tests

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
cryptoTls "crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/openziti/edge-api/rest_model"
"github.com/openziti/identity/certtools"
"github.com/openziti/ziti/common/eid"
"gopkg.in/resty.v1"
"net/http"
"testing"
"time"
)

func Test_EnrollmentCreate(t *testing.T) {
func Test_EnrollmentOtt(t *testing.T) {
ctx := NewTestContext(t)
defer ctx.Teardown()
ctx.StartServer()
Expand Down Expand Up @@ -83,6 +91,89 @@ func Test_EnrollmentCreate(t *testing.T) {
})
})

t.Run("can not enroll with an expired enrollment", func(t *testing.T) {
ctx.testContextChanged(t)

identity := ctx.AdminManagementSession.requireNewIdentity(false)

expiresAt := time.Now().Add(5 * time.Second).UTC()
enrollmentCreate := &rest_model.EnrollmentCreate{
IdentityID: &identity.Id,
Method: S(rest_model.EnrollmentCreateMethodOtt),
ExpiresAt: ST(expiresAt),
}

enrollmentCreateResp := &rest_model.CreateEnvelope{}

resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(enrollmentCreate).SetResult(enrollmentCreateResp).Post("/enrollments")
ctx.NoError(err)
ctx.Equal(http.StatusCreated, resp.StatusCode(), string(resp.Body()))
ctx.NotNil(enrollmentCreateResp)
ctx.NotNil(enrollmentCreateResp.Data)
ctx.NotEmpty(enrollmentCreateResp.Data.ID)

t.Run("enrollment has the proper values", func(t *testing.T) {
ctx.testContextChanged(t)
enrollmentGetResp := &rest_model.DetailEnrollmentEnvelope{}

resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(enrollmentGetResp).Get("/enrollments/" + enrollmentCreateResp.Data.ID)
ctx.NoError(err)
ctx.Equal(http.StatusOK, resp.StatusCode(), string(resp.Body()))
ctx.NotNil(enrollmentGetResp)
ctx.NotNil(enrollmentGetResp.Data)
ctx.NotNil(enrollmentGetResp.Data.ID)
ctx.NotNil(enrollmentGetResp.Data.Method)
ctx.NotNil(enrollmentGetResp.Data.ExpiresAt)
ctx.NotNil(enrollmentGetResp.Data.Identity)
ctx.NotNil(enrollmentGetResp.Data.Token)
ctx.NotEmpty(*enrollmentGetResp.Data.Token)
ctx.NotEmpty(enrollmentGetResp.Data.IdentityID)
ctx.NotEmpty(enrollmentGetResp.Data.JWT)

ctx.Equal(*enrollmentCreate.Method, *enrollmentGetResp.Data.Method)
ctx.Equal(enrollmentCreate.ExpiresAt.String(), enrollmentGetResp.Data.ExpiresAt.String())
ctx.Equal(*enrollmentCreate.IdentityID, enrollmentGetResp.Data.IdentityID)
})

t.Run("can not enroll", func(t *testing.T) {
ctx.testContextChanged(t)
duration := time.Until(expiresAt)

if duration > 0 {
time.Sleep(duration)
}

result := ctx.AdminManagementSession.requireQuery(fmt.Sprintf("identities/%v", identity.Id))

tokenValue := result.Path("data.enrollment.ott.token")

ctx.Req.NotNil(tokenValue)
token, ok := tokenValue.Data().(string)
ctx.Req.True(ok)

privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
ctx.Req.NoError(err)

request, err := certtools.NewCertRequest(map[string]string{
"C": "US", "O": "NetFoundry-API-Test", "CN": identity.Id,
}, nil)
ctx.Req.NoError(err)

csr, err := x509.CreateCertificateRequest(rand.Reader, request, privateKey)
ctx.Req.NoError(err)

csrPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr})

enrollResp, err := ctx.newAnonymousClientApiRequest().
SetBody(csrPem).
SetHeader("content-type", "application/x-pem-file").
SetHeader("accept", "application/json").
Post("enroll?token=" + token)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusBadRequest, enrollResp.StatusCode())
})
})

t.Run("can not create two OTT enrollments", func(t *testing.T) {
ctx.testContextChanged(t)

Expand Down Expand Up @@ -182,17 +273,17 @@ func Test_EnrollmentCreate(t *testing.T) {
t.Run("can create an OTTCA enrollment", func(t *testing.T) {
ctx.testContextChanged(t)

identity := ctx.AdminManagementSession.requireNewIdentity(false)
ca := newTestCa()
testIdentity := ctx.AdminManagementSession.requireNewIdentity(false)
testCa := newTestCa()

caCreate := &rest_model.CaCreate{
CertPem: &ca.certPem,
IdentityNameFormat: ca.identityNameFormat,
IdentityRoles: ca.identityRoles,
IsAuthEnabled: &ca.isAuthEnabled,
IsAutoCaEnrollmentEnabled: &ca.isAutoCaEnrollmentEnabled,
IsOttCaEnrollmentEnabled: &ca.isOttCaEnrollmentEnabled,
Name: &ca.name,
CertPem: &testCa.certPem,
IdentityNameFormat: testCa.identityNameFormat,
IdentityRoles: testCa.identityRoles,
IsAuthEnabled: &testCa.isAuthEnabled,
IsAutoCaEnrollmentEnabled: &testCa.isAutoCaEnrollmentEnabled,
IsOttCaEnrollmentEnabled: &testCa.isOttCaEnrollmentEnabled,
Name: &testCa.name,
}

caCreateResp := &rest_model.CreateEnvelope{}
Expand All @@ -214,7 +305,7 @@ func Test_EnrollmentCreate(t *testing.T) {
ctx.NotNil(caGetResp.Data.ID)
ctx.NotEmpty(caGetResp.Data.VerificationToken)

verifyCert, _, err := generateCaSignedClientCert(ca.publicCert, ca.privateKey, caGetResp.Data.VerificationToken.String())
verifyCert, _, err := generateCaSignedClientCert(testCa.publicCert, testCa.privateKey, caGetResp.Data.VerificationToken.String())
ctx.Req.NoError(err)

verificationBlock := &pem.Block{
Expand All @@ -228,7 +319,7 @@ func Test_EnrollmentCreate(t *testing.T) {
standardJsonResponseTests(resp, http.StatusOK, t)

enrollmentCreate := &rest_model.EnrollmentCreate{
IdentityID: &identity.Id,
IdentityID: &testIdentity.Id,
CaID: S(caCreateResp.Data.ID),
Method: S(rest_model.EnrollmentCreateMethodOttca),
ExpiresAt: ST(time.Now().Add(1 * time.Hour).UTC()),
Expand Down Expand Up @@ -270,12 +361,134 @@ func Test_EnrollmentCreate(t *testing.T) {
t.Run("can enroll", func(t *testing.T) {
ctx.testContextChanged(t)

clientAuthenticator := ca.CreateSignedCert(eid.New())
clientAuthenticator := testCa.CreateSignedCert(eid.New())

ctx.completeOttCaEnrollment(clientAuthenticator)
})
})

t.Run("can not enroll with an expired OTTCA enrollment", func(t *testing.T) {
ctx.testContextChanged(t)

testId := ctx.AdminManagementSession.requireNewIdentity(false)
testCa := newTestCa()

caCreate := &rest_model.CaCreate{
CertPem: &testCa.certPem,
IdentityNameFormat: testCa.identityNameFormat,
IdentityRoles: testCa.identityRoles,
IsAuthEnabled: &testCa.isAuthEnabled,
IsAutoCaEnrollmentEnabled: &testCa.isAutoCaEnrollmentEnabled,
IsOttCaEnrollmentEnabled: &testCa.isOttCaEnrollmentEnabled,
Name: &testCa.name,
}

caCreateResp := &rest_model.CreateEnvelope{}

resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(caCreate).SetResult(caCreateResp).Post("cas/")
ctx.NoError(err)
ctx.Equal(http.StatusCreated, resp.StatusCode(), string(resp.Body()))
ctx.NotNil(caCreateResp)
ctx.NotNil(caCreateResp.Data)
ctx.NotEmpty(caCreateResp.Data.ID)

caGetResp := &rest_model.DetailCaEnvelope{}

resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(caGetResp).Get("/cas/" + caCreateResp.Data.ID)
ctx.NoError(err)
ctx.Equal(http.StatusOK, resp.StatusCode(), string(resp.Body()))
ctx.NotNil(caGetResp)
ctx.NotNil(caGetResp.Data)
ctx.NotNil(caGetResp.Data.ID)
ctx.NotEmpty(caGetResp.Data.VerificationToken)

verifyCert, _, err := generateCaSignedClientCert(testCa.publicCert, testCa.privateKey, caGetResp.Data.VerificationToken.String())
ctx.Req.NoError(err)

verificationBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: verifyCert.Raw,
}
verifyPem := pem.EncodeToMemory(verificationBlock)

resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetHeader("content-type", "text/plain").SetBody(verifyPem).Post("cas/" + caCreateResp.Data.ID + "/verify")
ctx.Req.NoError(err)
standardJsonResponseTests(resp, http.StatusOK, t)

expiresAt := time.Now().Add(5 * time.Second).UTC()
enrollmentCreate := &rest_model.EnrollmentCreate{
IdentityID: &testId.Id,
CaID: S(caCreateResp.Data.ID),
Method: S(rest_model.EnrollmentCreateMethodOttca),
ExpiresAt: ST(expiresAt),
}

enrollmentCreateResp := &rest_model.CreateEnvelope{}

resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(enrollmentCreate).SetResult(enrollmentCreateResp).Post("/enrollments")
ctx.NoError(err)
ctx.Equal(http.StatusCreated, resp.StatusCode(), string(resp.Body()))
ctx.NotNil(enrollmentCreateResp)
ctx.NotNil(enrollmentCreateResp.Data)
ctx.NotEmpty(enrollmentCreateResp.Data.ID)

t.Run("enrollment has the proper values", func(t *testing.T) {
ctx.testContextChanged(t)
enrollmentGetResp := &rest_model.DetailEnrollmentEnvelope{}

resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(enrollmentGetResp).Get("/enrollments/" + enrollmentCreateResp.Data.ID)
ctx.NoError(err)
ctx.Equal(http.StatusOK, resp.StatusCode(), string(resp.Body()))
ctx.NotNil(enrollmentGetResp)
ctx.NotNil(enrollmentGetResp.Data)
ctx.NotNil(enrollmentGetResp.Data.ID)
ctx.NotNil(enrollmentGetResp.Data.Method)
ctx.NotNil(enrollmentGetResp.Data.ExpiresAt)
ctx.NotNil(enrollmentGetResp.Data.Identity)
ctx.NotNil(enrollmentGetResp.Data.Token)
ctx.NotEmpty(*enrollmentGetResp.Data.Token)
ctx.NotEmpty(enrollmentGetResp.Data.IdentityID)
ctx.NotEmpty(enrollmentGetResp.Data.JWT)

ctx.Equal(*enrollmentCreate.Method, *enrollmentGetResp.Data.Method)
ctx.Equal(enrollmentCreate.ExpiresAt.String(), enrollmentGetResp.Data.ExpiresAt.String())
ctx.Equal(*enrollmentCreate.IdentityID, enrollmentGetResp.Data.IdentityID)
ctx.Equal(*enrollmentCreate.CaID, *enrollmentGetResp.Data.CaID)

t.Run("can not enroll", func(t *testing.T) {
ctx.testContextChanged(t)

clientAuthenticator := testCa.CreateSignedCert(eid.New())

ctx.completeOttCaEnrollment(clientAuthenticator)

trans := ctx.NewTransport()
trans.TLSClientConfig.Certificates = []cryptoTls.Certificate{
{
Certificate: [][]byte{clientAuthenticator.cert.Raw},
PrivateKey: clientAuthenticator.key,
},
}
client := resty.NewWithClient(ctx.NewHttpClient(trans))
client.SetHostURL("https://" + ctx.ApiHost + EdgeClientApiPath)

duration := time.Until(expiresAt)

if duration > 0 {
time.Sleep(duration)
}

enrollResp, err := client.NewRequest().
SetBody("{}").
SetHeader("content-type", "application/x-pem-file").
Post("enroll?method=ottca&token=" + *enrollmentGetResp.Data.Token)
ctx.Req.NoError(err)
ctx.Req.Equal(http.StatusBadRequest, enrollResp.StatusCode())
})
})

})

t.Run("can not create two OTTCA enrollments", func(t *testing.T) {
ctx.testContextChanged(t)

Expand Down
1 change: 1 addition & 0 deletions tests/enrollment_refresh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ func Test_EnrollmentRefresh(t *testing.T) {

ctx.Req.NotEqual(origIdentityGetEnv.Data.Enrollment.Ott.JWT, updatedIdentityGetEnv.Data.Enrollment.Ott.JWT)
ctx.Req.NotEqual(origIdentityGetEnv.Data.Enrollment.Ott.ExpiresAt.String(), updatedIdentityGetEnv.Data.Enrollment.Ott.ExpiresAt.String())
ctx.Req.NotEqual(origIdentityGetEnv.Data.Enrollment.Ott.Token, updatedIdentityGetEnv.Data.Enrollment.Ott.Token)
ctx.Req.Equal(refreshPost.ExpiresAt.String(), updatedIdentityGetEnv.Data.Enrollment.Ott.ExpiresAt.String())
})
})
Expand Down

0 comments on commit da3674f

Please sign in to comment.