Skip to content

Commit 6f63430

Browse files
authored
Merge pull request #7016 from FlowFuse/housekeeping-sso-cert-expiry
Add Housekeeping task to check SSO certificate expiry
2 parents 73d584f + 21712dd commit 6f63430

4 files changed

Lines changed: 113 additions & 0 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Sent when a SSO Cert will expire in under 2 weeks
2+
3+
// Inserts:
4+
// - name: SSO profile name
5+
// - data: Date of cert Expiry
6+
7+
module.exports = {
8+
subject: 'FlowFuse SSO Certificate expiring soon',
9+
text:
10+
`Hello {{{user.name}}},
11+
12+
The certificate for the SSO Profile "{{name}}" will expire
13+
at {{{date}}}.
14+
15+
Please talk to your SSO administrator about issuing a new
16+
certificate for this provider.
17+
18+
Your friendly FlowFuse Team
19+
`,
20+
html:
21+
`
22+
<p>Hello {{{user.name}}},</p>
23+
24+
<p>The certificate for the SSO Profile "{{name}}" will expire
25+
at {{{date}}}.</p
26+
27+
<p>Please talk to your SSO administrator about issuing a new
28+
certificate for this provider.</p>
29+
30+
<p>Your friendly FlowFuse Team</p>
31+
`
32+
}

forge/ee/lib/sso/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ module.exports.init = async function (app) {
77
// Set the SSO feature flag
88
app.config.features.register('sso', true, true)
99

10+
app.postoffice.registerTemplate('SSOCertsExpiring', require('./emailTemplates/SSOCertsExpiring'))
11+
app.housekeeper.registerTask(require('./tasks/saml-cert-check'))
12+
1013
async function getProviderOptions (id) {
1114
const provider = await app.db.models.SAMLProvider.byId(id)
1215
if (provider) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Check when SSO certs expire
3+
*/
4+
const crypto = require('crypto')
5+
6+
const TWO_WEEKS = (60 * 60 * 24 * 14 * 1000)
7+
8+
module.exports = {
9+
name: 'checkSAMLCertificateExpiry',
10+
startup: false,
11+
delay: 5000,
12+
schedule: '@weekly',
13+
run: async function (app) {
14+
app.log.info('Checking SSO Cert life')
15+
try {
16+
const ssoConfigs = await app.db.models.SAMLProvider.getAll()
17+
for (const sso of ssoConfigs.providers) {
18+
if (sso.active && sso.type === 'saml') {
19+
if (sso.options.cert) {
20+
try {
21+
let pem = sso.options.cert
22+
if (!pem.startsWith('-----BEGIN CERTIFICATE-----\n')) {
23+
pem = `-----BEGIN CERTIFICATE-----\n${pem}\n-----END CERTIFICATE-----\n`
24+
}
25+
const cert = new crypto.X509Certificate(pem)
26+
const expiry = Date.parse(cert.validTo)
27+
const life = expiry - Date.now()
28+
if (life < TWO_WEEKS) {
29+
app.log.info(`SSO Certificate expires soon ${sso.name} ${cert.validTo}`)
30+
await emailAdmins(app, 'SSOCertsExpiring', { name: sso.name, date: cert.validTo })
31+
}
32+
} catch (err) {
33+
app.log.debug(`Problem checking ${sso.name}'s SSO certificate ${err.toString()}`)
34+
}
35+
}
36+
}
37+
}
38+
} catch (err) {
39+
app.log.debug(`Problem checking SSO certificate ${err.toString()}`)
40+
}
41+
}
42+
}
43+
44+
async function emailAdmins (app, template, context) {
45+
const admins = await app.db.models.User.admins()
46+
for (const admin of admins) {
47+
await app.postoffice.send(admin, template, context)
48+
}
49+
}

test/unit/forge/ee/lib/sso/index_spec.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,25 @@ d
5757
e`
5858
}
5959
})
60+
app.samlProviders.provider5 = await app.db.models.SAMLProvider.create({
61+
name: 'expired SAML Cert',
62+
domainFilter: 'certtest3.com',
63+
active: true,
64+
options: {
65+
cert: `-----BEGIN CERTIFICATE-----
66+
MIIBlTCCAT+gAwIBAgIUbplhRkxz5Bi/PNX0XVoDXweALacwDQYJKoZIhvcNAQEL
67+
BQAwHzEdMBsGA1UEAwwUZXhwaXJlZCBGRiBTQU1MIGNlcnQwHhcNMjYwNDA2MTUz
68+
NzMyWhcNMjYwNDA3MTUzNzMyWjAfMR0wGwYDVQQDDBRleHBpcmVkIEZGIFNBTUwg
69+
Y2VydDBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC8C1+SXu71Bfg/QDgYkdVlk4lG
70+
yB2R6TAuQcRmXhKpjSTFx8UZinwDIJsSvIHF7ogFwZxveOzeExIFw702Tm2LAgMB
71+
AAGjUzBRMB0GA1UdDgQWBBTWj9s9V3qdJGtSapocX7r3YhG86TAfBgNVHSMEGDAW
72+
gBTWj9s9V3qdJGtSapocX7r3YhG86TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
73+
DQEBCwUAA0EADtdIpFInwMnjjhv4AMaeFsAUnKJzGmd9Gg/ugd13Adto6ReEC7+s
74+
NOY6Z1oJnpttQ9gwyV8euQ3C0Wcjf3+OVQ==
75+
-----END CERTIFICATE-----
76+
`
77+
}
78+
})
6079
})
6180

6281
after(async function () {
@@ -444,4 +463,14 @@ d
444463
;(await app.db.models.TeamMember.getTeamMembership(app.user.id, teams.BTeam.id)).should.have.property('role', Roles.Owner)
445464
})
446465
})
466+
describe('find expired SSO SAML Certs', async function () {
467+
it('send email for active SSO profile with cert with less than 2 weeks life', async function () {
468+
const task = require('../../../../../../forge/ee/lib/sso/tasks/saml-cert-check')
469+
await task.run(app)
470+
app.config.email.transport.messages.should.have.length(1)
471+
const email = app.config.email.transport.messages[0]
472+
email.subject.should.equal('FlowFuse SSO Certificate expiring soon')
473+
email.text.should.match(/"expired SAML Cert" will expire/)
474+
})
475+
})
447476
})

0 commit comments

Comments
 (0)