@andrewheberle/serverless-ssh-ca 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +174 -1
  2. package/dist/api/operations.d.ts +50 -0
  3. package/dist/api/operations.d.ts.map +1 -0
  4. package/dist/api/operations.js +264 -0
  5. package/dist/api/operations.js.map +1 -0
  6. package/dist/api/schema.d.ts +72 -0
  7. package/dist/api/schema.d.ts.map +1 -0
  8. package/dist/api/schema.js +114 -0
  9. package/dist/api/schema.js.map +1 -0
  10. package/dist/api/v3/index.d.ts.map +1 -1
  11. package/dist/api/v3/index.js +13 -202
  12. package/dist/api/v3/index.js.map +1 -1
  13. package/dist/api/v3/schema.d.ts +23 -68
  14. package/dist/api/v3/schema.d.ts.map +1 -1
  15. package/dist/api/v3/schema.js +18 -104
  16. package/dist/api/v3/schema.js.map +1 -1
  17. package/dist/api/v4/index.d.ts +8 -0
  18. package/dist/api/v4/index.d.ts.map +1 -0
  19. package/dist/api/v4/index.js +155 -0
  20. package/dist/api/v4/index.js.map +1 -0
  21. package/dist/api/v4/schema.d.ts +268 -0
  22. package/dist/api/v4/schema.d.ts.map +1 -0
  23. package/dist/api/v4/schema.js +186 -0
  24. package/dist/api/v4/schema.js.map +1 -0
  25. package/dist/certificate.d.ts +9 -6
  26. package/dist/certificate.d.ts.map +1 -1
  27. package/dist/certificate.js +55 -99
  28. package/dist/certificate.js.map +1 -1
  29. package/dist/db/index.d.ts +68 -9
  30. package/dist/db/index.d.ts.map +1 -1
  31. package/dist/db/index.js +165 -49
  32. package/dist/db/index.js.map +1 -1
  33. package/dist/db/migrations/0003_krl_revision.d.ts +3 -0
  34. package/dist/db/migrations/0003_krl_revision.d.ts.map +1 -0
  35. package/dist/db/migrations/0003_krl_revision.js +14 -0
  36. package/dist/db/migrations/0003_krl_revision.js.map +1 -0
  37. package/dist/db/migrations/index.d.ts.map +1 -1
  38. package/dist/db/migrations/index.js +2 -0
  39. package/dist/db/migrations/index.js.map +1 -1
  40. package/dist/db/types.d.ts +2 -0
  41. package/dist/db/types.d.ts.map +1 -1
  42. package/dist/index.d.ts +5 -2
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +4 -2
  45. package/dist/index.js.map +1 -1
  46. package/dist/krl.d.ts +3 -2
  47. package/dist/krl.d.ts.map +1 -1
  48. package/dist/krl.js +12 -165
  49. package/dist/krl.js.map +1 -1
  50. package/dist/node/cache.d.ts +21 -0
  51. package/dist/node/cache.d.ts.map +1 -0
  52. package/dist/node/cache.js +49 -0
  53. package/dist/node/cache.js.map +1 -0
  54. package/dist/node/env.d.ts +6 -3
  55. package/dist/node/env.d.ts.map +1 -1
  56. package/dist/node/env.js +4 -3
  57. package/dist/node/env.js.map +1 -1
  58. package/dist/node/index.d.ts +1 -0
  59. package/dist/node/index.d.ts.map +1 -1
  60. package/dist/node/index.js +1 -0
  61. package/dist/node/index.js.map +1 -1
  62. package/dist/proof.d.ts +36 -7
  63. package/dist/proof.d.ts.map +1 -1
  64. package/dist/proof.js +53 -21
  65. package/dist/proof.js.map +1 -1
  66. package/dist/router.d.ts +25 -1
  67. package/dist/router.d.ts.map +1 -1
  68. package/dist/router.js +41 -2
  69. package/dist/router.js.map +1 -1
  70. package/dist/ssh/certificate.d.ts +93 -0
  71. package/dist/ssh/certificate.d.ts.map +1 -0
  72. package/dist/ssh/certificate.js +178 -0
  73. package/dist/ssh/certificate.js.map +1 -0
  74. package/dist/ssh/encoding.d.ts +41 -0
  75. package/dist/ssh/encoding.d.ts.map +1 -0
  76. package/dist/ssh/encoding.js +70 -0
  77. package/dist/ssh/encoding.js.map +1 -0
  78. package/dist/ssh/keys.d.ts +92 -0
  79. package/dist/ssh/keys.d.ts.map +1 -0
  80. package/dist/ssh/keys.js +277 -0
  81. package/dist/ssh/keys.js.map +1 -0
  82. package/dist/ssh/signer.d.ts +35 -0
  83. package/dist/ssh/signer.d.ts.map +1 -0
  84. package/dist/ssh/signer.js +99 -0
  85. package/dist/ssh/signer.js.map +1 -0
  86. package/dist/types.d.ts +64 -19
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/utils.d.ts +107 -21
  89. package/dist/utils.d.ts.map +1 -1
  90. package/dist/utils.js +251 -65
  91. package/dist/utils.js.map +1 -1
  92. package/dist/verify.d.ts +17 -5
  93. package/dist/verify.d.ts.map +1 -1
  94. package/dist/verify.js +47 -9
  95. package/dist/verify.js.map +1 -1
  96. package/openapi.json +1942 -1
  97. package/package.json +4 -3
  98. package/src/api/operations.ts +314 -0
  99. package/src/api/schema.ts +146 -0
  100. package/src/api/v3/index.ts +21 -248
  101. package/src/api/v3/schema.ts +36 -128
  102. package/src/api/v4/index.ts +266 -0
  103. package/src/api/v4/schema.ts +241 -0
  104. package/src/certificate.ts +62 -123
  105. package/src/db/index.ts +196 -56
  106. package/src/db/migrations/0003_krl_revision.ts +15 -0
  107. package/src/db/migrations/index.ts +2 -0
  108. package/src/db/types.ts +2 -0
  109. package/src/index.ts +6 -4
  110. package/src/krl.ts +13 -197
  111. package/src/node/cache.ts +62 -0
  112. package/src/node/env.ts +6 -5
  113. package/src/node/index.ts +1 -0
  114. package/src/proof.ts +65 -31
  115. package/src/router.ts +67 -3
  116. package/src/ssh/certificate.ts +236 -0
  117. package/src/ssh/encoding.ts +81 -0
  118. package/src/ssh/keys.ts +327 -0
  119. package/src/ssh/signer.ts +129 -0
  120. package/src/types.ts +60 -20
  121. package/src/utils.ts +285 -81
  122. package/src/verify.ts +54 -14
package/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Serverless SSH CA API",
5
- "version": "3.0.0"
5
+ "version": "4.0.0"
6
6
  },
7
7
  "components": {
8
8
  "securitySchemes": {
@@ -203,6 +203,103 @@
203
203
  "public_key",
204
204
  "proof"
205
205
  ]
206
+ },
207
+ "User Certificate Issue Request": {
208
+ "type": "object",
209
+ "properties": {
210
+ "public_key": {
211
+ "type": "string",
212
+ "format": "byte",
213
+ "description": "SSH public key to sign",
214
+ "example": "ZWNkc2Etc2hhMi1uaXN0cDI1NiBBQUFBRTJWalpITmhMWE5vWVRJdGJtbHpkSEF5TlRZQUFBQUlibWx6ZEhBeU5UWUFBQUJCQkdNcFNKdHRmMEl0dE5DVmMyOFR6WEZSMUQweHZPM25wNjdWemVBUXNiZFpza3JEY1lqU2x3SjZIUERtVHBYYVUwbEVhWDlMNFloYy9jQ2YxTWU5RlRrPQo="
215
+ },
216
+ "identity": {
217
+ "type": "string",
218
+ "description": "Identity Token JWT from OIDC IdP",
219
+ "example": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIiwiaWF0IjoxNzc1NzAzMDk1fQ.ri_neAWnhMNK3LzlsrBcQYymSM4yRjmNSZZSeZiXhrEqtEz6c3cXk0Esq765umGjpUsWcosL-OFrDJlyAjTDnhrd9oV08uc_CW0rQRsJIGEuRo3ryxkLdVu9mGoZWEUb9KwjGJrwxvr-0cPWx5jaDyKwJcqMvtV_bEITUD51sDB1Vm89QfYRO_pGJo2vrRzSvMjpUenRpwPay4lYIBxl41_4YpR9Rc6VrIZuYsjV2iqEZ4eBrygMA7zPR_hN7l7s95FddLOzj5NsK57VT4uLHwYohx2oqMzw3M-B9HsZIQin_9q61pZFQXepzJth0woXiZheU27llnfHX967PhNQyg"
220
+ },
221
+ "extensions": {
222
+ "type": "array",
223
+ "items": {
224
+ "type": "string"
225
+ },
226
+ "default": [
227
+ "permit-X11-forwarding",
228
+ "permit-agent-forwarding",
229
+ "permit-port-forwarding",
230
+ "permit-pty",
231
+ "permit-user-rc"
232
+ ],
233
+ "description": "Extensions to include in the issued SSH certificate"
234
+ },
235
+ "lifetime": {
236
+ "type": "integer",
237
+ "minimum": 300,
238
+ "maximum": 86400,
239
+ "default": 86400,
240
+ "description": "Lifetime of issued SSH certificate"
241
+ }
242
+ },
243
+ "required": [
244
+ "public_key",
245
+ "identity"
246
+ ]
247
+ },
248
+ "Host Certificate Issue Request": {
249
+ "type": "object",
250
+ "properties": {
251
+ "public_key": {
252
+ "type": "string",
253
+ "format": "byte",
254
+ "description": "SSH public key to sign",
255
+ "example": "ZWNkc2Etc2hhMi1uaXN0cDI1NiBBQUFBRTJWalpITmhMWE5vWVRJdGJtbHpkSEF5TlRZQUFBQUlibWx6ZEhBeU5UWUFBQUJCQkdNcFNKdHRmMEl0dE5DVmMyOFR6WEZSMUQweHZPM25wNjdWemVBUXNiZFpza3JEY1lqU2x3SjZIUERtVHBYYVUwbEVhWDlMNFloYy9jQ2YxTWU5RlRrPQo="
256
+ },
257
+ "identity": {
258
+ "type": "string",
259
+ "description": "Identity Token JWT from OIDC IdP",
260
+ "example": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIiwiaWF0IjoxNzc1NzAzMDk1fQ.ri_neAWnhMNK3LzlsrBcQYymSM4yRjmNSZZSeZiXhrEqtEz6c3cXk0Esq765umGjpUsWcosL-OFrDJlyAjTDnhrd9oV08uc_CW0rQRsJIGEuRo3ryxkLdVu9mGoZWEUb9KwjGJrwxvr-0cPWx5jaDyKwJcqMvtV_bEITUD51sDB1Vm89QfYRO_pGJo2vrRzSvMjpUenRpwPay4lYIBxl41_4YpR9Rc6VrIZuYsjV2iqEZ4eBrygMA7zPR_hN7l7s95FddLOzj5NsK57VT4uLHwYohx2oqMzw3M-B9HsZIQin_9q61pZFQXepzJth0woXiZheU27llnfHX967PhNQyg"
261
+ },
262
+ "principals": {
263
+ "type": "array",
264
+ "items": {
265
+ "type": "string"
266
+ },
267
+ "minItems": 1,
268
+ "description": "List of principals to include on the issued certificate"
269
+ },
270
+ "lifetime": {
271
+ "type": "integer",
272
+ "minimum": 86400,
273
+ "maximum": 2592000,
274
+ "default": 2592000,
275
+ "description": "Lifetime of issued Host SSH certificate"
276
+ }
277
+ },
278
+ "required": [
279
+ "public_key",
280
+ "identity",
281
+ "principals"
282
+ ]
283
+ },
284
+ "Host Certificate Renewal Request": {
285
+ "type": "object",
286
+ "properties": {
287
+ "certificate": {
288
+ "type": "string",
289
+ "format": "byte",
290
+ "description": "SSH certificate to renew, which must have the serial number in the path"
291
+ },
292
+ "lifetime": {
293
+ "type": "integer",
294
+ "minimum": 86400,
295
+ "maximum": 2592000,
296
+ "default": 2592000,
297
+ "description": "Lifetime of renewed Host SSH certificate"
298
+ }
299
+ },
300
+ "required": [
301
+ "certificate"
302
+ ]
206
303
  }
207
304
  },
208
305
  "parameters": {}
@@ -213,6 +310,8 @@
213
310
  "tags": [
214
311
  "CA"
215
312
  ],
313
+ "summary": "Get the CA public key",
314
+ "description": "Returns the public key of the SSH Certificate Authority in OpenSSH format, for use in `TrustedUserCAKeys` on SSH servers or `@cert-authority` entries in `known_hosts` on SSH clients.",
216
315
  "operationId": "get_ca",
217
316
  "responses": {
218
317
  "200": {
@@ -273,6 +372,8 @@
273
372
  "tags": [
274
373
  "User"
275
374
  ],
375
+ "summary": "Request a user certificate",
376
+ "description": "Issues a signed SSH user certificate for the provided public key. The request must be authenticated with an access token and include an identity token from the OIDC IdP for the same subject, along with a proof of possession of the private key. Principals on the issued certificate are derived from the identity token.",
276
377
  "security": [
277
378
  {
278
379
  "oidcAuth": []
@@ -486,6 +587,46 @@
486
587
  }
487
588
  }
488
589
  },
590
+ "429": {
591
+ "description": "Too Many Requests",
592
+ "content": {
593
+ "application/json": {
594
+ "schema": {
595
+ "type": "object",
596
+ "properties": {
597
+ "success": {
598
+ "type": "boolean",
599
+ "enum": [
600
+ false
601
+ ]
602
+ },
603
+ "errors": {
604
+ "type": "array",
605
+ "items": {
606
+ "type": "object",
607
+ "properties": {
608
+ "code": {
609
+ "type": "number"
610
+ },
611
+ "message": {
612
+ "type": "string"
613
+ }
614
+ },
615
+ "required": [
616
+ "code",
617
+ "message"
618
+ ]
619
+ }
620
+ }
621
+ },
622
+ "required": [
623
+ "success",
624
+ "errors"
625
+ ]
626
+ }
627
+ }
628
+ }
629
+ },
489
630
  "500": {
490
631
  "description": "Internal Server Error",
491
632
  "content": {
@@ -535,6 +676,7 @@
535
676
  "User"
536
677
  ],
537
678
  "summary": "Get the Key Revocation List for user certificates",
679
+ "description": "Returns the OpenSSH Key Revocation List (KRL) of revoked user certificates issued by this CA, along with an SSHSIG signature made by the CA key so the KRL can be verified before use.",
538
680
  "operationId": "get_user_krl",
539
681
  "responses": {
540
682
  "200": {
@@ -596,6 +738,7 @@
596
738
  "User"
597
739
  ],
598
740
  "summary": "Revoke a user certificate",
741
+ "description": "Revokes a user certificate issued by this CA, identified by its serial number, and adds it to the user Key Revocation List. The request must include a proof of possession of the private key for the public key that the certificate was issued for.",
599
742
  "operationId": "post_user_revoke",
600
743
  "requestBody": {
601
744
  "content": {
@@ -831,6 +974,46 @@
831
974
  }
832
975
  }
833
976
  },
977
+ "429": {
978
+ "description": "Too Many Requests",
979
+ "content": {
980
+ "application/json": {
981
+ "schema": {
982
+ "type": "object",
983
+ "properties": {
984
+ "success": {
985
+ "type": "boolean",
986
+ "enum": [
987
+ false
988
+ ]
989
+ },
990
+ "errors": {
991
+ "type": "array",
992
+ "items": {
993
+ "type": "object",
994
+ "properties": {
995
+ "code": {
996
+ "type": "number"
997
+ },
998
+ "message": {
999
+ "type": "string"
1000
+ }
1001
+ },
1002
+ "required": [
1003
+ "code",
1004
+ "message"
1005
+ ]
1006
+ }
1007
+ }
1008
+ },
1009
+ "required": [
1010
+ "success",
1011
+ "errors"
1012
+ ]
1013
+ }
1014
+ }
1015
+ }
1016
+ },
834
1017
  "500": {
835
1018
  "description": "Internal Server Error",
836
1019
  "content": {
@@ -879,6 +1062,8 @@
879
1062
  "tags": [
880
1063
  "Host"
881
1064
  ],
1065
+ "summary": "Request a host certificate",
1066
+ "description": "Issues a signed SSH host certificate for the provided public key and principals. The request must be authenticated with an access token and include an identity token from the OIDC IdP for the same subject, along with a proof of possession of the private key. The user must be permitted to request host certificates by email address or role.",
882
1067
  "security": [
883
1068
  {
884
1069
  "oidcAuth": []
@@ -1092,6 +1277,46 @@
1092
1277
  }
1093
1278
  }
1094
1279
  },
1280
+ "429": {
1281
+ "description": "Too Many Requests",
1282
+ "content": {
1283
+ "application/json": {
1284
+ "schema": {
1285
+ "type": "object",
1286
+ "properties": {
1287
+ "success": {
1288
+ "type": "boolean",
1289
+ "enum": [
1290
+ false
1291
+ ]
1292
+ },
1293
+ "errors": {
1294
+ "type": "array",
1295
+ "items": {
1296
+ "type": "object",
1297
+ "properties": {
1298
+ "code": {
1299
+ "type": "number"
1300
+ },
1301
+ "message": {
1302
+ "type": "string"
1303
+ }
1304
+ },
1305
+ "required": [
1306
+ "code",
1307
+ "message"
1308
+ ]
1309
+ }
1310
+ }
1311
+ },
1312
+ "required": [
1313
+ "success",
1314
+ "errors"
1315
+ ]
1316
+ }
1317
+ }
1318
+ }
1319
+ },
1095
1320
  "500": {
1096
1321
  "description": "Internal Server Error",
1097
1322
  "content": {
@@ -1140,6 +1365,8 @@
1140
1365
  "tags": [
1141
1366
  "Host"
1142
1367
  ],
1368
+ "summary": "Renew a host certificate",
1369
+ "description": "Issues a new SSH host certificate with the same principals as an existing, unexpired and unrevoked host certificate issued by this CA. No OIDC authentication is required; instead the public key must match the existing certificate and the proof of possession must be made with its private key. The lifetime of the renewed certificate is no longer than that of the existing certificate. The existing certificate is revoked, so it cannot be used or renewed again once the renewed certificate is installed.",
1143
1370
  "operationId": "post_host_renew",
1144
1371
  "requestBody": {
1145
1372
  "content": {
@@ -1335,6 +1562,46 @@
1335
1562
  }
1336
1563
  }
1337
1564
  },
1565
+ "429": {
1566
+ "description": "Too Many Requests",
1567
+ "content": {
1568
+ "application/json": {
1569
+ "schema": {
1570
+ "type": "object",
1571
+ "properties": {
1572
+ "success": {
1573
+ "type": "boolean",
1574
+ "enum": [
1575
+ false
1576
+ ]
1577
+ },
1578
+ "errors": {
1579
+ "type": "array",
1580
+ "items": {
1581
+ "type": "object",
1582
+ "properties": {
1583
+ "code": {
1584
+ "type": "number"
1585
+ },
1586
+ "message": {
1587
+ "type": "string"
1588
+ }
1589
+ },
1590
+ "required": [
1591
+ "code",
1592
+ "message"
1593
+ ]
1594
+ }
1595
+ }
1596
+ },
1597
+ "required": [
1598
+ "success",
1599
+ "errors"
1600
+ ]
1601
+ }
1602
+ }
1603
+ }
1604
+ },
1338
1605
  "500": {
1339
1606
  "description": "Internal Server Error",
1340
1607
  "content": {
@@ -1384,6 +1651,7 @@
1384
1651
  "Host"
1385
1652
  ],
1386
1653
  "summary": "Get the Key Revocation List for host certificates",
1654
+ "description": "Returns the OpenSSH Key Revocation List (KRL) of revoked host certificates issued by this CA, along with an SSHSIG signature made by the CA key so the KRL can be verified before use.",
1387
1655
  "operationId": "get_host_krl",
1388
1656
  "responses": {
1389
1657
  "200": {
@@ -1445,6 +1713,7 @@
1445
1713
  "Host"
1446
1714
  ],
1447
1715
  "summary": "Revoke a host certificate",
1716
+ "description": "Revokes a host certificate issued by this CA, identified by its serial number, and adds it to the host Key Revocation List. The request must include a proof of possession of the private key for the public key that the certificate was issued for.",
1448
1717
  "operationId": "post_host_revoke",
1449
1718
  "requestBody": {
1450
1719
  "content": {
@@ -1680,6 +1949,1678 @@
1680
1949
  }
1681
1950
  }
1682
1951
  },
1952
+ "429": {
1953
+ "description": "Too Many Requests",
1954
+ "content": {
1955
+ "application/json": {
1956
+ "schema": {
1957
+ "type": "object",
1958
+ "properties": {
1959
+ "success": {
1960
+ "type": "boolean",
1961
+ "enum": [
1962
+ false
1963
+ ]
1964
+ },
1965
+ "errors": {
1966
+ "type": "array",
1967
+ "items": {
1968
+ "type": "object",
1969
+ "properties": {
1970
+ "code": {
1971
+ "type": "number"
1972
+ },
1973
+ "message": {
1974
+ "type": "string"
1975
+ }
1976
+ },
1977
+ "required": [
1978
+ "code",
1979
+ "message"
1980
+ ]
1981
+ }
1982
+ }
1983
+ },
1984
+ "required": [
1985
+ "success",
1986
+ "errors"
1987
+ ]
1988
+ }
1989
+ }
1990
+ }
1991
+ },
1992
+ "500": {
1993
+ "description": "Internal Server Error",
1994
+ "content": {
1995
+ "application/json": {
1996
+ "schema": {
1997
+ "type": "object",
1998
+ "properties": {
1999
+ "success": {
2000
+ "type": "boolean",
2001
+ "enum": [
2002
+ false
2003
+ ]
2004
+ },
2005
+ "errors": {
2006
+ "type": "array",
2007
+ "items": {
2008
+ "type": "object",
2009
+ "properties": {
2010
+ "code": {
2011
+ "type": "number"
2012
+ },
2013
+ "message": {
2014
+ "type": "string"
2015
+ }
2016
+ },
2017
+ "required": [
2018
+ "code",
2019
+ "message"
2020
+ ]
2021
+ }
2022
+ }
2023
+ },
2024
+ "required": [
2025
+ "success",
2026
+ "errors"
2027
+ ]
2028
+ }
2029
+ }
2030
+ }
2031
+ }
2032
+ }
2033
+ }
2034
+ },
2035
+ "/api/v4/ca": {
2036
+ "get": {
2037
+ "tags": [
2038
+ "CA"
2039
+ ],
2040
+ "summary": "Get the CA public key",
2041
+ "description": "Returns the public key of the SSH Certificate Authority in OpenSSH format, for use in `TrustedUserCAKeys` on SSH servers or `@cert-authority` entries in `known_hosts` on SSH clients.",
2042
+ "operationId": "get_ca_public_key",
2043
+ "responses": {
2044
+ "200": {
2045
+ "description": "Returns SSH Certificate Authority Public Key",
2046
+ "content": {
2047
+ "text/plain": {
2048
+ "schema": {
2049
+ "type": "string"
2050
+ }
2051
+ }
2052
+ }
2053
+ },
2054
+ "500": {
2055
+ "description": "Internal Server Error",
2056
+ "content": {
2057
+ "application/json": {
2058
+ "schema": {
2059
+ "type": "object",
2060
+ "properties": {
2061
+ "success": {
2062
+ "type": "boolean",
2063
+ "enum": [
2064
+ false
2065
+ ]
2066
+ },
2067
+ "errors": {
2068
+ "type": "array",
2069
+ "items": {
2070
+ "type": "object",
2071
+ "properties": {
2072
+ "code": {
2073
+ "type": "number"
2074
+ },
2075
+ "message": {
2076
+ "type": "string"
2077
+ }
2078
+ },
2079
+ "required": [
2080
+ "code",
2081
+ "message"
2082
+ ]
2083
+ }
2084
+ }
2085
+ },
2086
+ "required": [
2087
+ "success",
2088
+ "errors"
2089
+ ]
2090
+ }
2091
+ }
2092
+ }
2093
+ }
2094
+ }
2095
+ }
2096
+ },
2097
+ "/api/v4/user/certificates": {
2098
+ "post": {
2099
+ "tags": [
2100
+ "User"
2101
+ ],
2102
+ "operationId": "issue_user_certificate",
2103
+ "summary": "Request a user certificate",
2104
+ "description": "Issues a signed SSH user certificate for the provided public key. The request must be authenticated with an access token and include an identity token from the OIDC IdP for the same subject, along with a proof of possession of the private key for `public_key` in the `Proof-Of-Possession` header made for this request. Principals on the issued certificate are derived from the identity token.",
2105
+ "security": [
2106
+ {
2107
+ "oidcAuth": []
2108
+ }
2109
+ ],
2110
+ "parameters": [
2111
+ {
2112
+ "schema": {
2113
+ "type": "string",
2114
+ "description": "Access Token JWT from OIDC IdP",
2115
+ "example": "Bearer eyJ..."
2116
+ },
2117
+ "required": true,
2118
+ "description": "Access Token JWT from OIDC IdP",
2119
+ "name": "Authorization",
2120
+ "in": "header"
2121
+ },
2122
+ {
2123
+ "schema": {
2124
+ "type": "string",
2125
+ "description": "Proof of possession of the private key comprising of `${timestamp}.${fingerprint}.${signature}`. The signature is a BASE64 encoded armored SSHSIG signature, made with the `request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace, of `${timestamp}.${fingerprint}.${METHOD} ${path}` where `${METHOD} ${path}` is the request the proof is for, for example `DELETE /api/v4/user/certificates/12345678901234567890`. The timestamp is in milliseconds since the Unix epoch.",
2126
+ "example": "1765696780805.SHA256:79mq0wtpQMTvS4+Of8VzLN0qmYWNUyTXYmqKwhEgSLs.LS0tLS1CRUdJTiBTU0ggU0lHTkFUVVJFLS0tLS0K..."
2127
+ },
2128
+ "required": true,
2129
+ "description": "Proof of possession of the private key comprising of `${timestamp}.${fingerprint}.${signature}`. The signature is a BASE64 encoded armored SSHSIG signature, made with the `request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace, of `${timestamp}.${fingerprint}.${METHOD} ${path}` where `${METHOD} ${path}` is the request the proof is for, for example `DELETE /api/v4/user/certificates/12345678901234567890`. The timestamp is in milliseconds since the Unix epoch.",
2130
+ "name": "Proof-Of-Possession",
2131
+ "in": "header"
2132
+ }
2133
+ ],
2134
+ "requestBody": {
2135
+ "content": {
2136
+ "application/json": {
2137
+ "schema": {
2138
+ "$ref": "#/components/schemas/User Certificate Issue Request"
2139
+ }
2140
+ }
2141
+ }
2142
+ },
2143
+ "responses": {
2144
+ "200": {
2145
+ "description": "SSH User Certificate issued successfully",
2146
+ "content": {
2147
+ "application/json": {
2148
+ "schema": {
2149
+ "$ref": "#/components/schemas/Certificate Response"
2150
+ }
2151
+ }
2152
+ }
2153
+ },
2154
+ "400": {
2155
+ "description": "Input Validation Error",
2156
+ "content": {
2157
+ "application/json": {
2158
+ "schema": {
2159
+ "type": "object",
2160
+ "properties": {
2161
+ "success": {
2162
+ "type": "boolean",
2163
+ "enum": [
2164
+ false
2165
+ ]
2166
+ },
2167
+ "errors": {
2168
+ "type": "array",
2169
+ "items": {
2170
+ "type": "object",
2171
+ "properties": {
2172
+ "code": {
2173
+ "type": "number"
2174
+ },
2175
+ "message": {
2176
+ "type": "string"
2177
+ },
2178
+ "path": {
2179
+ "type": "array",
2180
+ "items": {
2181
+ "type": "string"
2182
+ }
2183
+ }
2184
+ },
2185
+ "required": [
2186
+ "code",
2187
+ "message",
2188
+ "path"
2189
+ ]
2190
+ }
2191
+ }
2192
+ },
2193
+ "required": [
2194
+ "success",
2195
+ "errors"
2196
+ ]
2197
+ }
2198
+ }
2199
+ }
2200
+ },
2201
+ "401": {
2202
+ "description": "Unauthorized",
2203
+ "content": {
2204
+ "application/json": {
2205
+ "schema": {
2206
+ "type": "object",
2207
+ "properties": {
2208
+ "success": {
2209
+ "type": "boolean",
2210
+ "enum": [
2211
+ false
2212
+ ]
2213
+ },
2214
+ "errors": {
2215
+ "type": "array",
2216
+ "items": {
2217
+ "type": "object",
2218
+ "properties": {
2219
+ "code": {
2220
+ "type": "number"
2221
+ },
2222
+ "message": {
2223
+ "type": "string"
2224
+ }
2225
+ },
2226
+ "required": [
2227
+ "code",
2228
+ "message"
2229
+ ]
2230
+ }
2231
+ }
2232
+ },
2233
+ "required": [
2234
+ "success",
2235
+ "errors"
2236
+ ]
2237
+ }
2238
+ }
2239
+ }
2240
+ },
2241
+ "403": {
2242
+ "description": "Forbidden",
2243
+ "content": {
2244
+ "application/json": {
2245
+ "schema": {
2246
+ "type": "object",
2247
+ "properties": {
2248
+ "success": {
2249
+ "type": "boolean",
2250
+ "enum": [
2251
+ false
2252
+ ]
2253
+ },
2254
+ "errors": {
2255
+ "type": "array",
2256
+ "items": {
2257
+ "type": "object",
2258
+ "properties": {
2259
+ "code": {
2260
+ "type": "number"
2261
+ },
2262
+ "message": {
2263
+ "type": "string"
2264
+ }
2265
+ },
2266
+ "required": [
2267
+ "code",
2268
+ "message"
2269
+ ]
2270
+ }
2271
+ }
2272
+ },
2273
+ "required": [
2274
+ "success",
2275
+ "errors"
2276
+ ]
2277
+ }
2278
+ }
2279
+ }
2280
+ },
2281
+ "422": {
2282
+ "description": "Unprocessable Entity",
2283
+ "content": {
2284
+ "application/json": {
2285
+ "schema": {
2286
+ "type": "object",
2287
+ "properties": {
2288
+ "success": {
2289
+ "type": "boolean",
2290
+ "enum": [
2291
+ false
2292
+ ]
2293
+ },
2294
+ "errors": {
2295
+ "type": "array",
2296
+ "items": {
2297
+ "type": "object",
2298
+ "properties": {
2299
+ "code": {
2300
+ "type": "number"
2301
+ },
2302
+ "message": {
2303
+ "type": "string"
2304
+ },
2305
+ "path": {
2306
+ "type": "array",
2307
+ "items": {
2308
+ "type": "string"
2309
+ }
2310
+ }
2311
+ },
2312
+ "required": [
2313
+ "code",
2314
+ "message",
2315
+ "path"
2316
+ ]
2317
+ }
2318
+ }
2319
+ },
2320
+ "required": [
2321
+ "success",
2322
+ "errors"
2323
+ ]
2324
+ }
2325
+ }
2326
+ }
2327
+ },
2328
+ "429": {
2329
+ "description": "Too Many Requests",
2330
+ "content": {
2331
+ "application/json": {
2332
+ "schema": {
2333
+ "type": "object",
2334
+ "properties": {
2335
+ "success": {
2336
+ "type": "boolean",
2337
+ "enum": [
2338
+ false
2339
+ ]
2340
+ },
2341
+ "errors": {
2342
+ "type": "array",
2343
+ "items": {
2344
+ "type": "object",
2345
+ "properties": {
2346
+ "code": {
2347
+ "type": "number"
2348
+ },
2349
+ "message": {
2350
+ "type": "string"
2351
+ }
2352
+ },
2353
+ "required": [
2354
+ "code",
2355
+ "message"
2356
+ ]
2357
+ }
2358
+ }
2359
+ },
2360
+ "required": [
2361
+ "success",
2362
+ "errors"
2363
+ ]
2364
+ }
2365
+ }
2366
+ }
2367
+ },
2368
+ "500": {
2369
+ "description": "Internal Server Error",
2370
+ "content": {
2371
+ "application/json": {
2372
+ "schema": {
2373
+ "type": "object",
2374
+ "properties": {
2375
+ "success": {
2376
+ "type": "boolean",
2377
+ "enum": [
2378
+ false
2379
+ ]
2380
+ },
2381
+ "errors": {
2382
+ "type": "array",
2383
+ "items": {
2384
+ "type": "object",
2385
+ "properties": {
2386
+ "code": {
2387
+ "type": "number"
2388
+ },
2389
+ "message": {
2390
+ "type": "string"
2391
+ }
2392
+ },
2393
+ "required": [
2394
+ "code",
2395
+ "message"
2396
+ ]
2397
+ }
2398
+ }
2399
+ },
2400
+ "required": [
2401
+ "success",
2402
+ "errors"
2403
+ ]
2404
+ }
2405
+ }
2406
+ }
2407
+ }
2408
+ }
2409
+ }
2410
+ },
2411
+ "/api/v4/user/certificates/{serial}": {
2412
+ "delete": {
2413
+ "tags": [
2414
+ "User"
2415
+ ],
2416
+ "operationId": "revoke_user_certificate",
2417
+ "summary": "Revoke a user certificate",
2418
+ "description": "Revokes a user certificate issued by this CA and adds it to the user Key Revocation List. The `Proof-Of-Possession` header must contain a proof of possession of the private key for the public key that the certificate was issued for, made for this request. A certificate issued for a different public key is reported as not found.",
2419
+ "parameters": [
2420
+ {
2421
+ "schema": {
2422
+ "type": "string",
2423
+ "pattern": "^(0|[1-9]\\d*)$",
2424
+ "description": "Serial number of the certificate",
2425
+ "example": "12345678901234567890"
2426
+ },
2427
+ "required": true,
2428
+ "description": "Serial number of the certificate",
2429
+ "name": "serial",
2430
+ "in": "path"
2431
+ },
2432
+ {
2433
+ "schema": {
2434
+ "type": "string",
2435
+ "description": "Proof of possession of the private key comprising of `${timestamp}.${fingerprint}.${signature}`. The signature is a BASE64 encoded armored SSHSIG signature, made with the `request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace, of `${timestamp}.${fingerprint}.${METHOD} ${path}` where `${METHOD} ${path}` is the request the proof is for, for example `DELETE /api/v4/user/certificates/12345678901234567890`. The timestamp is in milliseconds since the Unix epoch.",
2436
+ "example": "1765696780805.SHA256:79mq0wtpQMTvS4+Of8VzLN0qmYWNUyTXYmqKwhEgSLs.LS0tLS1CRUdJTiBTU0ggU0lHTkFUVVJFLS0tLS0K..."
2437
+ },
2438
+ "required": true,
2439
+ "description": "Proof of possession of the private key comprising of `${timestamp}.${fingerprint}.${signature}`. The signature is a BASE64 encoded armored SSHSIG signature, made with the `request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace, of `${timestamp}.${fingerprint}.${METHOD} ${path}` where `${METHOD} ${path}` is the request the proof is for, for example `DELETE /api/v4/user/certificates/12345678901234567890`. The timestamp is in milliseconds since the Unix epoch.",
2440
+ "name": "Proof-Of-Possession",
2441
+ "in": "header"
2442
+ }
2443
+ ],
2444
+ "responses": {
2445
+ "200": {
2446
+ "description": "SSH user certificate revoked successfully",
2447
+ "content": {
2448
+ "application/json": {
2449
+ "schema": {
2450
+ "$ref": "#/components/schemas/Revocation Response"
2451
+ }
2452
+ }
2453
+ }
2454
+ },
2455
+ "400": {
2456
+ "description": "Input Validation Error",
2457
+ "content": {
2458
+ "application/json": {
2459
+ "schema": {
2460
+ "type": "object",
2461
+ "properties": {
2462
+ "success": {
2463
+ "type": "boolean",
2464
+ "enum": [
2465
+ false
2466
+ ]
2467
+ },
2468
+ "errors": {
2469
+ "type": "array",
2470
+ "items": {
2471
+ "type": "object",
2472
+ "properties": {
2473
+ "code": {
2474
+ "type": "number"
2475
+ },
2476
+ "message": {
2477
+ "type": "string"
2478
+ },
2479
+ "path": {
2480
+ "type": "array",
2481
+ "items": {
2482
+ "type": "string"
2483
+ }
2484
+ }
2485
+ },
2486
+ "required": [
2487
+ "code",
2488
+ "message",
2489
+ "path"
2490
+ ]
2491
+ }
2492
+ }
2493
+ },
2494
+ "required": [
2495
+ "success",
2496
+ "errors"
2497
+ ]
2498
+ }
2499
+ }
2500
+ }
2501
+ },
2502
+ "404": {
2503
+ "description": "Not Found",
2504
+ "content": {
2505
+ "application/json": {
2506
+ "schema": {
2507
+ "type": "object",
2508
+ "properties": {
2509
+ "success": {
2510
+ "type": "boolean",
2511
+ "enum": [
2512
+ false
2513
+ ]
2514
+ },
2515
+ "errors": {
2516
+ "type": "array",
2517
+ "items": {
2518
+ "type": "object",
2519
+ "properties": {
2520
+ "code": {
2521
+ "type": "number"
2522
+ },
2523
+ "message": {
2524
+ "type": "string"
2525
+ }
2526
+ },
2527
+ "required": [
2528
+ "code",
2529
+ "message"
2530
+ ]
2531
+ }
2532
+ }
2533
+ },
2534
+ "required": [
2535
+ "success",
2536
+ "errors"
2537
+ ]
2538
+ }
2539
+ }
2540
+ }
2541
+ },
2542
+ "409": {
2543
+ "description": "Conflict",
2544
+ "content": {
2545
+ "application/json": {
2546
+ "schema": {
2547
+ "type": "object",
2548
+ "properties": {
2549
+ "success": {
2550
+ "type": "boolean",
2551
+ "enum": [
2552
+ false
2553
+ ]
2554
+ },
2555
+ "errors": {
2556
+ "type": "array",
2557
+ "items": {
2558
+ "type": "object",
2559
+ "properties": {
2560
+ "code": {
2561
+ "type": "number"
2562
+ },
2563
+ "message": {
2564
+ "type": "string"
2565
+ }
2566
+ },
2567
+ "required": [
2568
+ "code",
2569
+ "message"
2570
+ ]
2571
+ }
2572
+ }
2573
+ },
2574
+ "required": [
2575
+ "success",
2576
+ "errors"
2577
+ ]
2578
+ }
2579
+ }
2580
+ }
2581
+ },
2582
+ "429": {
2583
+ "description": "Too Many Requests",
2584
+ "content": {
2585
+ "application/json": {
2586
+ "schema": {
2587
+ "type": "object",
2588
+ "properties": {
2589
+ "success": {
2590
+ "type": "boolean",
2591
+ "enum": [
2592
+ false
2593
+ ]
2594
+ },
2595
+ "errors": {
2596
+ "type": "array",
2597
+ "items": {
2598
+ "type": "object",
2599
+ "properties": {
2600
+ "code": {
2601
+ "type": "number"
2602
+ },
2603
+ "message": {
2604
+ "type": "string"
2605
+ }
2606
+ },
2607
+ "required": [
2608
+ "code",
2609
+ "message"
2610
+ ]
2611
+ }
2612
+ }
2613
+ },
2614
+ "required": [
2615
+ "success",
2616
+ "errors"
2617
+ ]
2618
+ }
2619
+ }
2620
+ }
2621
+ },
2622
+ "500": {
2623
+ "description": "Internal Server Error",
2624
+ "content": {
2625
+ "application/json": {
2626
+ "schema": {
2627
+ "type": "object",
2628
+ "properties": {
2629
+ "success": {
2630
+ "type": "boolean",
2631
+ "enum": [
2632
+ false
2633
+ ]
2634
+ },
2635
+ "errors": {
2636
+ "type": "array",
2637
+ "items": {
2638
+ "type": "object",
2639
+ "properties": {
2640
+ "code": {
2641
+ "type": "number"
2642
+ },
2643
+ "message": {
2644
+ "type": "string"
2645
+ }
2646
+ },
2647
+ "required": [
2648
+ "code",
2649
+ "message"
2650
+ ]
2651
+ }
2652
+ }
2653
+ },
2654
+ "required": [
2655
+ "success",
2656
+ "errors"
2657
+ ]
2658
+ }
2659
+ }
2660
+ }
2661
+ }
2662
+ }
2663
+ }
2664
+ },
2665
+ "/api/v4/user/krl": {
2666
+ "get": {
2667
+ "tags": [
2668
+ "User"
2669
+ ],
2670
+ "summary": "Get the Key Revocation List for user certificates",
2671
+ "description": "Returns the OpenSSH Key Revocation List (KRL) of revoked user certificates issued by this CA, along with an SSHSIG signature made by the CA key so the KRL can be verified before use.",
2672
+ "operationId": "get_user_revocation_list",
2673
+ "responses": {
2674
+ "200": {
2675
+ "description": "Returns an Open SSH Key Revocation List of revoked user certificates as BASE64 and an SSHSIG signature for verification",
2676
+ "content": {
2677
+ "application/json": {
2678
+ "schema": {
2679
+ "$ref": "#/components/schemas/Key Revocation List Response"
2680
+ }
2681
+ }
2682
+ }
2683
+ },
2684
+ "500": {
2685
+ "description": "Internal Server Error",
2686
+ "content": {
2687
+ "application/json": {
2688
+ "schema": {
2689
+ "type": "object",
2690
+ "properties": {
2691
+ "success": {
2692
+ "type": "boolean",
2693
+ "enum": [
2694
+ false
2695
+ ]
2696
+ },
2697
+ "errors": {
2698
+ "type": "array",
2699
+ "items": {
2700
+ "type": "object",
2701
+ "properties": {
2702
+ "code": {
2703
+ "type": "number"
2704
+ },
2705
+ "message": {
2706
+ "type": "string"
2707
+ }
2708
+ },
2709
+ "required": [
2710
+ "code",
2711
+ "message"
2712
+ ]
2713
+ }
2714
+ }
2715
+ },
2716
+ "required": [
2717
+ "success",
2718
+ "errors"
2719
+ ]
2720
+ }
2721
+ }
2722
+ }
2723
+ }
2724
+ }
2725
+ }
2726
+ },
2727
+ "/api/v4/host/certificates": {
2728
+ "post": {
2729
+ "tags": [
2730
+ "Host"
2731
+ ],
2732
+ "operationId": "issue_host_certificate",
2733
+ "summary": "Request a host certificate",
2734
+ "description": "Issues a signed SSH host certificate for the provided public key and principals. The request must be authenticated with an access token and include an identity token from the OIDC IdP for the same subject, along with a proof of possession of the private key for `public_key` in the `Proof-Of-Possession` header made for this request. The user must be permitted to request host certificates by email address or role.",
2735
+ "security": [
2736
+ {
2737
+ "oidcAuth": []
2738
+ }
2739
+ ],
2740
+ "parameters": [
2741
+ {
2742
+ "schema": {
2743
+ "type": "string",
2744
+ "description": "Access Token JWT from OIDC IdP",
2745
+ "example": "Bearer eyJ..."
2746
+ },
2747
+ "required": true,
2748
+ "description": "Access Token JWT from OIDC IdP",
2749
+ "name": "Authorization",
2750
+ "in": "header"
2751
+ },
2752
+ {
2753
+ "schema": {
2754
+ "type": "string",
2755
+ "description": "Proof of possession of the private key comprising of `${timestamp}.${fingerprint}.${signature}`. The signature is a BASE64 encoded armored SSHSIG signature, made with the `request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace, of `${timestamp}.${fingerprint}.${METHOD} ${path}` where `${METHOD} ${path}` is the request the proof is for, for example `DELETE /api/v4/user/certificates/12345678901234567890`. The timestamp is in milliseconds since the Unix epoch.",
2756
+ "example": "1765696780805.SHA256:79mq0wtpQMTvS4+Of8VzLN0qmYWNUyTXYmqKwhEgSLs.LS0tLS1CRUdJTiBTU0ggU0lHTkFUVVJFLS0tLS0K..."
2757
+ },
2758
+ "required": true,
2759
+ "description": "Proof of possession of the private key comprising of `${timestamp}.${fingerprint}.${signature}`. The signature is a BASE64 encoded armored SSHSIG signature, made with the `request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace, of `${timestamp}.${fingerprint}.${METHOD} ${path}` where `${METHOD} ${path}` is the request the proof is for, for example `DELETE /api/v4/user/certificates/12345678901234567890`. The timestamp is in milliseconds since the Unix epoch.",
2760
+ "name": "Proof-Of-Possession",
2761
+ "in": "header"
2762
+ }
2763
+ ],
2764
+ "requestBody": {
2765
+ "content": {
2766
+ "application/json": {
2767
+ "schema": {
2768
+ "$ref": "#/components/schemas/Host Certificate Issue Request"
2769
+ }
2770
+ }
2771
+ }
2772
+ },
2773
+ "responses": {
2774
+ "200": {
2775
+ "description": "SSH Host Certificate issued successfully",
2776
+ "content": {
2777
+ "application/json": {
2778
+ "schema": {
2779
+ "$ref": "#/components/schemas/Certificate Response"
2780
+ }
2781
+ }
2782
+ }
2783
+ },
2784
+ "400": {
2785
+ "description": "Input Validation Error",
2786
+ "content": {
2787
+ "application/json": {
2788
+ "schema": {
2789
+ "type": "object",
2790
+ "properties": {
2791
+ "success": {
2792
+ "type": "boolean",
2793
+ "enum": [
2794
+ false
2795
+ ]
2796
+ },
2797
+ "errors": {
2798
+ "type": "array",
2799
+ "items": {
2800
+ "type": "object",
2801
+ "properties": {
2802
+ "code": {
2803
+ "type": "number"
2804
+ },
2805
+ "message": {
2806
+ "type": "string"
2807
+ },
2808
+ "path": {
2809
+ "type": "array",
2810
+ "items": {
2811
+ "type": "string"
2812
+ }
2813
+ }
2814
+ },
2815
+ "required": [
2816
+ "code",
2817
+ "message",
2818
+ "path"
2819
+ ]
2820
+ }
2821
+ }
2822
+ },
2823
+ "required": [
2824
+ "success",
2825
+ "errors"
2826
+ ]
2827
+ }
2828
+ }
2829
+ }
2830
+ },
2831
+ "401": {
2832
+ "description": "Unauthorized",
2833
+ "content": {
2834
+ "application/json": {
2835
+ "schema": {
2836
+ "type": "object",
2837
+ "properties": {
2838
+ "success": {
2839
+ "type": "boolean",
2840
+ "enum": [
2841
+ false
2842
+ ]
2843
+ },
2844
+ "errors": {
2845
+ "type": "array",
2846
+ "items": {
2847
+ "type": "object",
2848
+ "properties": {
2849
+ "code": {
2850
+ "type": "number"
2851
+ },
2852
+ "message": {
2853
+ "type": "string"
2854
+ }
2855
+ },
2856
+ "required": [
2857
+ "code",
2858
+ "message"
2859
+ ]
2860
+ }
2861
+ }
2862
+ },
2863
+ "required": [
2864
+ "success",
2865
+ "errors"
2866
+ ]
2867
+ }
2868
+ }
2869
+ }
2870
+ },
2871
+ "403": {
2872
+ "description": "Forbidden",
2873
+ "content": {
2874
+ "application/json": {
2875
+ "schema": {
2876
+ "type": "object",
2877
+ "properties": {
2878
+ "success": {
2879
+ "type": "boolean",
2880
+ "enum": [
2881
+ false
2882
+ ]
2883
+ },
2884
+ "errors": {
2885
+ "type": "array",
2886
+ "items": {
2887
+ "type": "object",
2888
+ "properties": {
2889
+ "code": {
2890
+ "type": "number"
2891
+ },
2892
+ "message": {
2893
+ "type": "string"
2894
+ }
2895
+ },
2896
+ "required": [
2897
+ "code",
2898
+ "message"
2899
+ ]
2900
+ }
2901
+ }
2902
+ },
2903
+ "required": [
2904
+ "success",
2905
+ "errors"
2906
+ ]
2907
+ }
2908
+ }
2909
+ }
2910
+ },
2911
+ "422": {
2912
+ "description": "Unprocessable Entity",
2913
+ "content": {
2914
+ "application/json": {
2915
+ "schema": {
2916
+ "type": "object",
2917
+ "properties": {
2918
+ "success": {
2919
+ "type": "boolean",
2920
+ "enum": [
2921
+ false
2922
+ ]
2923
+ },
2924
+ "errors": {
2925
+ "type": "array",
2926
+ "items": {
2927
+ "type": "object",
2928
+ "properties": {
2929
+ "code": {
2930
+ "type": "number"
2931
+ },
2932
+ "message": {
2933
+ "type": "string"
2934
+ },
2935
+ "path": {
2936
+ "type": "array",
2937
+ "items": {
2938
+ "type": "string"
2939
+ }
2940
+ }
2941
+ },
2942
+ "required": [
2943
+ "code",
2944
+ "message",
2945
+ "path"
2946
+ ]
2947
+ }
2948
+ }
2949
+ },
2950
+ "required": [
2951
+ "success",
2952
+ "errors"
2953
+ ]
2954
+ }
2955
+ }
2956
+ }
2957
+ },
2958
+ "429": {
2959
+ "description": "Too Many Requests",
2960
+ "content": {
2961
+ "application/json": {
2962
+ "schema": {
2963
+ "type": "object",
2964
+ "properties": {
2965
+ "success": {
2966
+ "type": "boolean",
2967
+ "enum": [
2968
+ false
2969
+ ]
2970
+ },
2971
+ "errors": {
2972
+ "type": "array",
2973
+ "items": {
2974
+ "type": "object",
2975
+ "properties": {
2976
+ "code": {
2977
+ "type": "number"
2978
+ },
2979
+ "message": {
2980
+ "type": "string"
2981
+ }
2982
+ },
2983
+ "required": [
2984
+ "code",
2985
+ "message"
2986
+ ]
2987
+ }
2988
+ }
2989
+ },
2990
+ "required": [
2991
+ "success",
2992
+ "errors"
2993
+ ]
2994
+ }
2995
+ }
2996
+ }
2997
+ },
2998
+ "500": {
2999
+ "description": "Internal Server Error",
3000
+ "content": {
3001
+ "application/json": {
3002
+ "schema": {
3003
+ "type": "object",
3004
+ "properties": {
3005
+ "success": {
3006
+ "type": "boolean",
3007
+ "enum": [
3008
+ false
3009
+ ]
3010
+ },
3011
+ "errors": {
3012
+ "type": "array",
3013
+ "items": {
3014
+ "type": "object",
3015
+ "properties": {
3016
+ "code": {
3017
+ "type": "number"
3018
+ },
3019
+ "message": {
3020
+ "type": "string"
3021
+ }
3022
+ },
3023
+ "required": [
3024
+ "code",
3025
+ "message"
3026
+ ]
3027
+ }
3028
+ }
3029
+ },
3030
+ "required": [
3031
+ "success",
3032
+ "errors"
3033
+ ]
3034
+ }
3035
+ }
3036
+ }
3037
+ }
3038
+ }
3039
+ }
3040
+ },
3041
+ "/api/v4/host/certificates/{serial}/renew": {
3042
+ "post": {
3043
+ "tags": [
3044
+ "Host"
3045
+ ],
3046
+ "operationId": "renew_host_certificate",
3047
+ "summary": "Renew a host certificate",
3048
+ "description": "Issues a new SSH host certificate for the same public key and principals as an existing, unexpired and unrevoked host certificate issued by this CA. No OIDC authentication is required; instead the `Proof-Of-Possession` header must contain a proof of possession of the private key for the existing certificate made for this request. The lifetime of the renewed certificate is no longer than that of the existing certificate. The existing certificate is revoked, so it cannot be used or renewed again once the renewed certificate is installed.",
3049
+ "parameters": [
3050
+ {
3051
+ "schema": {
3052
+ "type": "string",
3053
+ "pattern": "^(0|[1-9]\\d*)$",
3054
+ "description": "Serial number of the certificate",
3055
+ "example": "12345678901234567890"
3056
+ },
3057
+ "required": true,
3058
+ "description": "Serial number of the certificate",
3059
+ "name": "serial",
3060
+ "in": "path"
3061
+ },
3062
+ {
3063
+ "schema": {
3064
+ "type": "string",
3065
+ "description": "Proof of possession of the private key comprising of `${timestamp}.${fingerprint}.${signature}`. The signature is a BASE64 encoded armored SSHSIG signature, made with the `request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace, of `${timestamp}.${fingerprint}.${METHOD} ${path}` where `${METHOD} ${path}` is the request the proof is for, for example `DELETE /api/v4/user/certificates/12345678901234567890`. The timestamp is in milliseconds since the Unix epoch.",
3066
+ "example": "1765696780805.SHA256:79mq0wtpQMTvS4+Of8VzLN0qmYWNUyTXYmqKwhEgSLs.LS0tLS1CRUdJTiBTU0ggU0lHTkFUVVJFLS0tLS0K..."
3067
+ },
3068
+ "required": true,
3069
+ "description": "Proof of possession of the private key comprising of `${timestamp}.${fingerprint}.${signature}`. The signature is a BASE64 encoded armored SSHSIG signature, made with the `request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace, of `${timestamp}.${fingerprint}.${METHOD} ${path}` where `${METHOD} ${path}` is the request the proof is for, for example `DELETE /api/v4/user/certificates/12345678901234567890`. The timestamp is in milliseconds since the Unix epoch.",
3070
+ "name": "Proof-Of-Possession",
3071
+ "in": "header"
3072
+ }
3073
+ ],
3074
+ "requestBody": {
3075
+ "content": {
3076
+ "application/json": {
3077
+ "schema": {
3078
+ "$ref": "#/components/schemas/Host Certificate Renewal Request"
3079
+ }
3080
+ }
3081
+ }
3082
+ },
3083
+ "responses": {
3084
+ "200": {
3085
+ "description": "SSH Host Certificate renewed successfully",
3086
+ "content": {
3087
+ "application/json": {
3088
+ "schema": {
3089
+ "$ref": "#/components/schemas/Certificate Response"
3090
+ }
3091
+ }
3092
+ }
3093
+ },
3094
+ "400": {
3095
+ "description": "Input Validation Error",
3096
+ "content": {
3097
+ "application/json": {
3098
+ "schema": {
3099
+ "type": "object",
3100
+ "properties": {
3101
+ "success": {
3102
+ "type": "boolean",
3103
+ "enum": [
3104
+ false
3105
+ ]
3106
+ },
3107
+ "errors": {
3108
+ "type": "array",
3109
+ "items": {
3110
+ "type": "object",
3111
+ "properties": {
3112
+ "code": {
3113
+ "type": "number"
3114
+ },
3115
+ "message": {
3116
+ "type": "string"
3117
+ },
3118
+ "path": {
3119
+ "type": "array",
3120
+ "items": {
3121
+ "type": "string"
3122
+ }
3123
+ }
3124
+ },
3125
+ "required": [
3126
+ "code",
3127
+ "message",
3128
+ "path"
3129
+ ]
3130
+ }
3131
+ }
3132
+ },
3133
+ "required": [
3134
+ "success",
3135
+ "errors"
3136
+ ]
3137
+ }
3138
+ }
3139
+ }
3140
+ },
3141
+ "401": {
3142
+ "description": "Unauthorized",
3143
+ "content": {
3144
+ "application/json": {
3145
+ "schema": {
3146
+ "type": "object",
3147
+ "properties": {
3148
+ "success": {
3149
+ "type": "boolean",
3150
+ "enum": [
3151
+ false
3152
+ ]
3153
+ },
3154
+ "errors": {
3155
+ "type": "array",
3156
+ "items": {
3157
+ "type": "object",
3158
+ "properties": {
3159
+ "code": {
3160
+ "type": "number"
3161
+ },
3162
+ "message": {
3163
+ "type": "string"
3164
+ }
3165
+ },
3166
+ "required": [
3167
+ "code",
3168
+ "message"
3169
+ ]
3170
+ }
3171
+ }
3172
+ },
3173
+ "required": [
3174
+ "success",
3175
+ "errors"
3176
+ ]
3177
+ }
3178
+ }
3179
+ }
3180
+ },
3181
+ "403": {
3182
+ "description": "Forbidden",
3183
+ "content": {
3184
+ "application/json": {
3185
+ "schema": {
3186
+ "type": "object",
3187
+ "properties": {
3188
+ "success": {
3189
+ "type": "boolean",
3190
+ "enum": [
3191
+ false
3192
+ ]
3193
+ },
3194
+ "errors": {
3195
+ "type": "array",
3196
+ "items": {
3197
+ "type": "object",
3198
+ "properties": {
3199
+ "code": {
3200
+ "type": "number"
3201
+ },
3202
+ "message": {
3203
+ "type": "string"
3204
+ }
3205
+ },
3206
+ "required": [
3207
+ "code",
3208
+ "message"
3209
+ ]
3210
+ }
3211
+ }
3212
+ },
3213
+ "required": [
3214
+ "success",
3215
+ "errors"
3216
+ ]
3217
+ }
3218
+ }
3219
+ }
3220
+ },
3221
+ "422": {
3222
+ "description": "Unprocessable Entity",
3223
+ "content": {
3224
+ "application/json": {
3225
+ "schema": {
3226
+ "type": "object",
3227
+ "properties": {
3228
+ "success": {
3229
+ "type": "boolean",
3230
+ "enum": [
3231
+ false
3232
+ ]
3233
+ },
3234
+ "errors": {
3235
+ "type": "array",
3236
+ "items": {
3237
+ "type": "object",
3238
+ "properties": {
3239
+ "code": {
3240
+ "type": "number"
3241
+ },
3242
+ "message": {
3243
+ "type": "string"
3244
+ },
3245
+ "path": {
3246
+ "type": "array",
3247
+ "items": {
3248
+ "type": "string"
3249
+ }
3250
+ }
3251
+ },
3252
+ "required": [
3253
+ "code",
3254
+ "message",
3255
+ "path"
3256
+ ]
3257
+ }
3258
+ }
3259
+ },
3260
+ "required": [
3261
+ "success",
3262
+ "errors"
3263
+ ]
3264
+ }
3265
+ }
3266
+ }
3267
+ },
3268
+ "429": {
3269
+ "description": "Too Many Requests",
3270
+ "content": {
3271
+ "application/json": {
3272
+ "schema": {
3273
+ "type": "object",
3274
+ "properties": {
3275
+ "success": {
3276
+ "type": "boolean",
3277
+ "enum": [
3278
+ false
3279
+ ]
3280
+ },
3281
+ "errors": {
3282
+ "type": "array",
3283
+ "items": {
3284
+ "type": "object",
3285
+ "properties": {
3286
+ "code": {
3287
+ "type": "number"
3288
+ },
3289
+ "message": {
3290
+ "type": "string"
3291
+ }
3292
+ },
3293
+ "required": [
3294
+ "code",
3295
+ "message"
3296
+ ]
3297
+ }
3298
+ }
3299
+ },
3300
+ "required": [
3301
+ "success",
3302
+ "errors"
3303
+ ]
3304
+ }
3305
+ }
3306
+ }
3307
+ },
3308
+ "500": {
3309
+ "description": "Internal Server Error",
3310
+ "content": {
3311
+ "application/json": {
3312
+ "schema": {
3313
+ "type": "object",
3314
+ "properties": {
3315
+ "success": {
3316
+ "type": "boolean",
3317
+ "enum": [
3318
+ false
3319
+ ]
3320
+ },
3321
+ "errors": {
3322
+ "type": "array",
3323
+ "items": {
3324
+ "type": "object",
3325
+ "properties": {
3326
+ "code": {
3327
+ "type": "number"
3328
+ },
3329
+ "message": {
3330
+ "type": "string"
3331
+ }
3332
+ },
3333
+ "required": [
3334
+ "code",
3335
+ "message"
3336
+ ]
3337
+ }
3338
+ }
3339
+ },
3340
+ "required": [
3341
+ "success",
3342
+ "errors"
3343
+ ]
3344
+ }
3345
+ }
3346
+ }
3347
+ }
3348
+ }
3349
+ }
3350
+ },
3351
+ "/api/v4/host/certificates/{serial}": {
3352
+ "delete": {
3353
+ "tags": [
3354
+ "Host"
3355
+ ],
3356
+ "operationId": "revoke_host_certificate",
3357
+ "summary": "Revoke a host certificate",
3358
+ "description": "Revokes a host certificate issued by this CA and adds it to the host Key Revocation List. The `Proof-Of-Possession` header must contain a proof of possession of the private key for the public key that the certificate was issued for, made for this request. A certificate issued for a different public key is reported as not found.",
3359
+ "parameters": [
3360
+ {
3361
+ "schema": {
3362
+ "type": "string",
3363
+ "pattern": "^(0|[1-9]\\d*)$",
3364
+ "description": "Serial number of the certificate",
3365
+ "example": "12345678901234567890"
3366
+ },
3367
+ "required": true,
3368
+ "description": "Serial number of the certificate",
3369
+ "name": "serial",
3370
+ "in": "path"
3371
+ },
3372
+ {
3373
+ "schema": {
3374
+ "type": "string",
3375
+ "description": "Proof of possession of the private key comprising of `${timestamp}.${fingerprint}.${signature}`. The signature is a BASE64 encoded armored SSHSIG signature, made with the `request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace, of `${timestamp}.${fingerprint}.${METHOD} ${path}` where `${METHOD} ${path}` is the request the proof is for, for example `DELETE /api/v4/user/certificates/12345678901234567890`. The timestamp is in milliseconds since the Unix epoch.",
3376
+ "example": "1765696780805.SHA256:79mq0wtpQMTvS4+Of8VzLN0qmYWNUyTXYmqKwhEgSLs.LS0tLS1CRUdJTiBTU0ggU0lHTkFUVVJFLS0tLS0K..."
3377
+ },
3378
+ "required": true,
3379
+ "description": "Proof of possession of the private key comprising of `${timestamp}.${fingerprint}.${signature}`. The signature is a BASE64 encoded armored SSHSIG signature, made with the `request-proof-of-possession@com.github.serverless-ssh-ca.andrewheberle` namespace, of `${timestamp}.${fingerprint}.${METHOD} ${path}` where `${METHOD} ${path}` is the request the proof is for, for example `DELETE /api/v4/user/certificates/12345678901234567890`. The timestamp is in milliseconds since the Unix epoch.",
3380
+ "name": "Proof-Of-Possession",
3381
+ "in": "header"
3382
+ }
3383
+ ],
3384
+ "responses": {
3385
+ "200": {
3386
+ "description": "SSH host certificate revoked successfully",
3387
+ "content": {
3388
+ "application/json": {
3389
+ "schema": {
3390
+ "$ref": "#/components/schemas/Revocation Response"
3391
+ }
3392
+ }
3393
+ }
3394
+ },
3395
+ "400": {
3396
+ "description": "Input Validation Error",
3397
+ "content": {
3398
+ "application/json": {
3399
+ "schema": {
3400
+ "type": "object",
3401
+ "properties": {
3402
+ "success": {
3403
+ "type": "boolean",
3404
+ "enum": [
3405
+ false
3406
+ ]
3407
+ },
3408
+ "errors": {
3409
+ "type": "array",
3410
+ "items": {
3411
+ "type": "object",
3412
+ "properties": {
3413
+ "code": {
3414
+ "type": "number"
3415
+ },
3416
+ "message": {
3417
+ "type": "string"
3418
+ },
3419
+ "path": {
3420
+ "type": "array",
3421
+ "items": {
3422
+ "type": "string"
3423
+ }
3424
+ }
3425
+ },
3426
+ "required": [
3427
+ "code",
3428
+ "message",
3429
+ "path"
3430
+ ]
3431
+ }
3432
+ }
3433
+ },
3434
+ "required": [
3435
+ "success",
3436
+ "errors"
3437
+ ]
3438
+ }
3439
+ }
3440
+ }
3441
+ },
3442
+ "404": {
3443
+ "description": "Not Found",
3444
+ "content": {
3445
+ "application/json": {
3446
+ "schema": {
3447
+ "type": "object",
3448
+ "properties": {
3449
+ "success": {
3450
+ "type": "boolean",
3451
+ "enum": [
3452
+ false
3453
+ ]
3454
+ },
3455
+ "errors": {
3456
+ "type": "array",
3457
+ "items": {
3458
+ "type": "object",
3459
+ "properties": {
3460
+ "code": {
3461
+ "type": "number"
3462
+ },
3463
+ "message": {
3464
+ "type": "string"
3465
+ }
3466
+ },
3467
+ "required": [
3468
+ "code",
3469
+ "message"
3470
+ ]
3471
+ }
3472
+ }
3473
+ },
3474
+ "required": [
3475
+ "success",
3476
+ "errors"
3477
+ ]
3478
+ }
3479
+ }
3480
+ }
3481
+ },
3482
+ "409": {
3483
+ "description": "Conflict",
3484
+ "content": {
3485
+ "application/json": {
3486
+ "schema": {
3487
+ "type": "object",
3488
+ "properties": {
3489
+ "success": {
3490
+ "type": "boolean",
3491
+ "enum": [
3492
+ false
3493
+ ]
3494
+ },
3495
+ "errors": {
3496
+ "type": "array",
3497
+ "items": {
3498
+ "type": "object",
3499
+ "properties": {
3500
+ "code": {
3501
+ "type": "number"
3502
+ },
3503
+ "message": {
3504
+ "type": "string"
3505
+ }
3506
+ },
3507
+ "required": [
3508
+ "code",
3509
+ "message"
3510
+ ]
3511
+ }
3512
+ }
3513
+ },
3514
+ "required": [
3515
+ "success",
3516
+ "errors"
3517
+ ]
3518
+ }
3519
+ }
3520
+ }
3521
+ },
3522
+ "429": {
3523
+ "description": "Too Many Requests",
3524
+ "content": {
3525
+ "application/json": {
3526
+ "schema": {
3527
+ "type": "object",
3528
+ "properties": {
3529
+ "success": {
3530
+ "type": "boolean",
3531
+ "enum": [
3532
+ false
3533
+ ]
3534
+ },
3535
+ "errors": {
3536
+ "type": "array",
3537
+ "items": {
3538
+ "type": "object",
3539
+ "properties": {
3540
+ "code": {
3541
+ "type": "number"
3542
+ },
3543
+ "message": {
3544
+ "type": "string"
3545
+ }
3546
+ },
3547
+ "required": [
3548
+ "code",
3549
+ "message"
3550
+ ]
3551
+ }
3552
+ }
3553
+ },
3554
+ "required": [
3555
+ "success",
3556
+ "errors"
3557
+ ]
3558
+ }
3559
+ }
3560
+ }
3561
+ },
3562
+ "500": {
3563
+ "description": "Internal Server Error",
3564
+ "content": {
3565
+ "application/json": {
3566
+ "schema": {
3567
+ "type": "object",
3568
+ "properties": {
3569
+ "success": {
3570
+ "type": "boolean",
3571
+ "enum": [
3572
+ false
3573
+ ]
3574
+ },
3575
+ "errors": {
3576
+ "type": "array",
3577
+ "items": {
3578
+ "type": "object",
3579
+ "properties": {
3580
+ "code": {
3581
+ "type": "number"
3582
+ },
3583
+ "message": {
3584
+ "type": "string"
3585
+ }
3586
+ },
3587
+ "required": [
3588
+ "code",
3589
+ "message"
3590
+ ]
3591
+ }
3592
+ }
3593
+ },
3594
+ "required": [
3595
+ "success",
3596
+ "errors"
3597
+ ]
3598
+ }
3599
+ }
3600
+ }
3601
+ }
3602
+ }
3603
+ }
3604
+ },
3605
+ "/api/v4/host/krl": {
3606
+ "get": {
3607
+ "tags": [
3608
+ "Host"
3609
+ ],
3610
+ "summary": "Get the Key Revocation List for host certificates",
3611
+ "description": "Returns the OpenSSH Key Revocation List (KRL) of revoked host certificates issued by this CA, along with an SSHSIG signature made by the CA key so the KRL can be verified before use.",
3612
+ "operationId": "get_host_revocation_list",
3613
+ "responses": {
3614
+ "200": {
3615
+ "description": "Returns an Open SSH Key Revocation List of revoked host certificates as BASE64 and an SSHSIG signature for verification",
3616
+ "content": {
3617
+ "application/json": {
3618
+ "schema": {
3619
+ "$ref": "#/components/schemas/Key Revocation List Response"
3620
+ }
3621
+ }
3622
+ }
3623
+ },
1683
3624
  "500": {
1684
3625
  "description": "Internal Server Error",
1685
3626
  "content": {