-
Notifications
You must be signed in to change notification settings - Fork 13
Data Architecture
Starchart needs to store different pieces of information related to users, their subdomains, and TLS/SSL certificates.
Data is stored in MySQL (production) or SQLite (development/testing). We use both the Prisma ORM to work directly with data, and PowerDNS, which uses MySQL and SQLite via backends.
The exact data and schemas used in Starchart are still be designed. However, we provide an initial set of requirements for the data.
The system needs to manage and store a number of different types of data, including:
- Users
- Subdomains
- Certificates
Users are authenticated using Seneca's Azure Active Directory via SAML2. Once authenticated, the following information is known about a user (all specifics have been changed or revoked below):
{
"issuer": "https://sts.windows.net/...idp-uuid.../",
"inResponseTo": "_851...",
"sessionIndex": "_dfa...",
"nameID": "username@seneca-domain.ca",
"nameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
"http://schemas.microsoft.com/identity/claims/tenantid": "...app-uuid...",
"http://schemas.microsoft.com/identity/claims/objectidentifier": "...uuid...",
"http://schemas.microsoft.com/identity/claims/displayname": "Full Name",
"http://schemas.microsoft.com/identity/claims/identityprovider": "https://sts.windows.net/...app-uuid.../",
"http://schemas.microsoft.com/claims/authnmethodsreferences": "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname": "Firstname",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname": "Lastname",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "username@seneca-domain.ca",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "username@seneca-domain.ca",
"sAMAccountName": "username"
}
When a user logs into the system for the first time, a new User
record is created, and the following record is created using this information:
Column Name | Initial Value | Note |
---|---|---|
username |
"username" | Unique, Primary Key, the username portion of http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress |
email |
"username@address.com" | http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress |
name |
"Full Name" | http://schemas.microsoft.com/identity/claims/displayname |
createdAt |
Current Date and Time | |
expiresAt |
1 Year from Today | Unless renewed, expire in 1 year (i.e., delete all subdomains, revoke certificate, delete User) |
disabled |
false | Accounts are enabled by default, but can be disabled (subdomains disabled, account locked) |
A User
can have many Subdomain
s. The User
's Subdomain
s are stored in two places. First, in a Subdomain
table:
Column Name | Value | Note |
---|---|---|
username |
"username" | Foreign Key to User table |
subdomain |
"ccp555.username.example.com." | Canonical name of subdomain in PowerDNS (note trailing . ) |
description |
"CCP555 AWS Load Balancer" | Free-form information about how this domain is being used |
createdAt |
Current Date and Time |
The second place that subdomains are stored is in the PowerDNS authoritative DNS server using the HTTP API. PowerDNS also stores its information in the database, but is managed separately (i.e., not by us). PowerDNS stores subdomains as RRSet
objects within a Zone
(e.g., example.com
).
Each RRSet
object includes the following information:
Name | Value | Note |
---|---|---|
name |
"ccp555.username.example.com." | Canonical name of subdomain, matches Subdomain table |
type |
"CNAME" | DNS Record type, one of "A", "AAAA", or "CNAME" |
ttl |
3600 | DNS TTL in seconds |
record.content |
"some.external.domain" | Value of the DNS record (e.g., ip address, domain name) |
record.disabled |
false | Whether or not this subdomain is disabled (maps to disabled in User table |
PowerDNS allows an RRSet to include one or more comments, but we use our own tables (User
, Subdomain
) to keep track of this information.
Starchart allows a user to create a single, wildcard certificate for *.username.example.com
. This certificate is then usable for any current or future subdomain that the user might create.
The user initiates the creation of the certificate (i.e., it doesn't happen automatically). Upon successful creation, the following are created:
- Certificate Signing Request (CSR)
- Certificate
- Private Key
Each of these is a string in PEM form, for example:
-----BEGIN CERTIFICATE-----
MIIFMjCCAxoCCQCVordquLnq8TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQGEwJB
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMRQwEgYDVQQDEwtleGFtcGxlLmNvbTAeFw0xNzA5MTQxNDMzMTRa
Fw0xODA5MTQxNDMzMTRaMFsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0
YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxFDASBgNVBAMT
C2V4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwi2P
YBNGl1n78niRGDKgcsWK03TcTeVbQ1HztA57Rr1iDHAZNx3Mv4E/Sha8VKbKoshc
mUcOS3AlmbIZX+7+9c7lL2oD+vtUZF1YUR/69fWuO72wk6fKj/eofxH9Ud5KFje8
qrYZdJWKkPMdWlYgjD6qpA5wl60NiuxmUr44ADZDytqHzNThN3wrFruz74PcMfak
cSUMxkh98LuNeGtqHpEAw+wliko3oDD4PanvDvp5mRgiQVKHEGT7dm85Up+W1iJK
J65fkc/j940MaLbdISZYYCT5dtPgCGKCHgVuVrY+OXFJrD3TTm94ILsR/BkS/VSK
NigGVPXg3q8tgIS++k13CzLUO0PNRMuod1RD9j5NEc2CVic9rcH06ugZyHlOcuVv
vRsPGd52BPn+Jf1aePKPPQHxT9i5GOs80CJw0eduZCDZB32biRYNwUtjFkHbu8ii
2IGkvhnWonjd4w5wOldG+RPr+XoFCIaHp5TszQ+HnUTLIXKtBgzzCKjK4eZqrck7
xpo5B5m5V7EUxBze2LYVky+GsDsqL8CggQqJL4ZKuZVoxgPwhnDy5nMs057NCU9E
nXcauMW9UEqEHu5NXnmGJrCvQ56wjYN3lgvCHEtmIpsRjCCWaBJYiawu1J5ZAf1y
GTVNh8pEvO//zL9ImUxrSfOGUeFiN1tzSFlTfbcCAwEAATANBgkqhkiG9w0BAQUF
AAOCAgEAdZZpgWv79CgF5ny6HmMaYgsXJKJyQE9RhJ1cmzDY8KAF+nzT7q4Pgt3W
bA9bpdji7C0WqKjX7hLipqhgFnqb8qZcodEKhX788qBj4X45+4nT6QipyJlz5x6K
cCn/v9gQNKks7U+dBlqquiVfbXaa1EAKMeGtqinf+Y51nR/fBcr/P9TBnSJqH61K
DO3qrE5KGTwHQ9VXoeKyeppGt5sYf8G0vwoHhtPTOO8TuLEIlFcXtzbC3zAtmQj6
Su//fI5yjuYTkiayxMx8nCGrQhQSXdC8gYpYd0os7UY01DVu4BTCXEvf0GYXtiGJ
eG8lQT/eu7WdK83uJ93U/BMYzoq4lSVcqY4LNxlfAQXKhaAbioA5XyT7co7FQ0g+
s2CGBUKa11wPDe8M2GVLPsxT2bXDQap5DQyVIuTwjtgL0tykGxPJPAnL2zuUy6T3
/YzrWaJ9Os+6mUCVdLnXtDgZ10Ujel7mq6wo9Ns+u07grXZkXpmJYnJXBrwOsY8K
Za5vFwgJrDXhWe+Fmgt1EP5VIqRCQAxH2iYvAaELi8udbN/ZiUU3K9t79MP/M3U/
tEWAubHXsaAv03jRy43X0VjlZHmagU/4dU7RBWfyuwRarYIXLNT2FCd2z4kd3fsL
3rB5iI+RH0uoNuOa1+UApfFCv0O65TYkp5jEWSlU8PhKYD43nXA=
-----END CERTIFICATE-----
We need to decide whether to store any of this information in the database on behalf of our users. There is no single approach here, and each option has both usability and security implications:
- We could store nothing, and simply present the certificates to the user upon creation. The user would be responsible for safely storing these, and not losing them. This has the benefit of not exposing the private key to anyone other than the user. It also means that we can't do automatic renewals. Let's Encrypt certificates expire in 90 days. The user would have to manually request a new certificate.
- We could store the certificate and private key in the database upon creation. This would decouple the initial "order" from the final "creation," which can take some time (though this is likely not a big deal). Storing this data means that anyone who has access to the database (e.g., administrators) would also have access. Storing the data would also enable automatic renewals, which requires the certificate and private key to be stored somewhere after a successful renewal completes.
If we go with option 1., we probably don't need any data storage about certificates, since everything would be done manually, and certificate storage would be up to the user.
If we go with option 2., we need to parse the resulting certificate (e.g., using X506Certificate) and store at least the following:
Column Name | Value | Note |
---|---|---|
id |
1 | Auto-Increment, Primary Key |
username |
"username" | Foreign Key to User table |
subject |
"CN=*.username.example.com" | The certificate's subject |
certificate |
"-----BEGIN CERTIFICATE-----..." | PEM formatted certificate |
privkey |
"-----BEGIN CERTIFICATE-----..." | PEM formatted private key |
validFrom |
Date and Time from which this certificate is valid | When the certificate was successfully created |
validTo |
Date and Time until which this certificate is valid | Let's Encrypt certificates expire in 90 days by default |
renewed |
false | Whether or not this certificate has been renewed, and whether or not to try renewing (again) |
NOTE: if we do renewals, a User
can have multiple certificates, some of which may be valid at the same time (e.g., if we renew within 30 days of expiry, both certificates would be valid for 30 days). We would probably only want to show user's valid certificate records in the UI.