feat: implement certificate validation and expiry detection (Phase 2.1)
Add comprehensive certificate metadata parsing and expiry warnings. ## Changes ### Types (src/types.ts) - Add CertificateInfo interface with validity dates, expiry status, issuer/subject, fingerprint ### Crypto Module (src/lib/crypto.ts) - Add parseCertificateInfo() to extract certificate metadata - Add isCertificateExpiringSoon() helper (default 30 days threshold) - Calculate SHA-256 fingerprint, parse DN fields, compute days until expiry ### SealingKeysView (src/components/SealingKeysView.tsx) - Display certificate expiry information in table - Show visual indicators: Expired (red), Expiring Soon (warning), Valid (normal) - Display days remaining for expiring certificates ### EncryptDialog (src/components/EncryptDialog.tsx) - Add expiry warning before encryption - Warn if certificate expired or expiring within 30 days - Show specific expiry date in warning message ## Features - **Certificate Parsing:** Extract all metadata from X.509 certificates - **Expiry Detection:** Automatic detection of expired/expiring certificates - **Visual Indicators:** Color-coded chips for expiry status - **Proactive Warnings:** Alert users before creating secrets with expiring certs - **SHA-256 Fingerprint:** Unique certificate identification ## Verification - TypeScript: 0 errors - Linting: 0 errors - Build: Success (343.95 kB, 94.58 kB gzipped) Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -25,7 +25,12 @@ import {
|
||||
import { useSnackbar } from 'notistack';
|
||||
import React from 'react';
|
||||
import { fetchPublicCertificate, getPluginConfig } from '../lib/controller';
|
||||
import { encryptKeyValues, parsePublicKeyFromCert } from '../lib/crypto';
|
||||
import {
|
||||
encryptKeyValues,
|
||||
isCertificateExpiringSoon,
|
||||
parseCertificateInfo,
|
||||
parsePublicKeyFromCert,
|
||||
} from '../lib/crypto';
|
||||
import { SealedSecret } from '../lib/SealedSecretCRD';
|
||||
import { validateSecretKey, validateSecretName, validateSecretValue } from '../lib/validators';
|
||||
import { PlaintextValue, SealedSecretScope, SecretKeyValue } from '../types';
|
||||
@@ -125,7 +130,27 @@ export function EncryptDialog({ open, onClose }: EncryptDialogProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Parse the public key
|
||||
// 2. Check certificate expiry
|
||||
const certInfoResult = parseCertificateInfo(certResult.value);
|
||||
if (certInfoResult.ok) {
|
||||
const certInfo = certInfoResult.value;
|
||||
|
||||
if (certInfo.isExpired) {
|
||||
enqueueSnackbar(
|
||||
`Warning: Controller certificate expired on ${certInfo.validTo.toLocaleDateString()}. ` +
|
||||
'Secrets may not be decryptable.',
|
||||
{ variant: 'warning' }
|
||||
);
|
||||
} else if (isCertificateExpiringSoon(certInfo, 30)) {
|
||||
enqueueSnackbar(
|
||||
`Warning: Controller certificate expires in ${certInfo.daysUntilExpiry} days ` +
|
||||
`(${certInfo.validTo.toLocaleDateString()}).`,
|
||||
{ variant: 'warning' }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Parse the public key
|
||||
const keyResult = parsePublicKeyFromCert(certResult.value);
|
||||
|
||||
if (keyResult.ok === false) {
|
||||
@@ -133,7 +158,7 @@ export function EncryptDialog({ open, onClose }: EncryptDialogProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Encrypt all values client-side
|
||||
// 4. Encrypt all values client-side
|
||||
const encryptResult = encryptKeyValues(
|
||||
keyResult.value,
|
||||
validKeyValues.map(kv => ({ key: kv.key, value: PlaintextValue(kv.value) })),
|
||||
@@ -147,7 +172,7 @@ export function EncryptDialog({ open, onClose }: EncryptDialogProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Construct the SealedSecret object
|
||||
// 5. Construct the SealedSecret object
|
||||
const sealedSecretData: any = {
|
||||
apiVersion: 'bitnami.com/v1alpha1',
|
||||
kind: 'SealedSecret',
|
||||
@@ -171,7 +196,7 @@ export function EncryptDialog({ open, onClose }: EncryptDialogProps) {
|
||||
sealedSecretData.metadata.annotations['sealedsecrets.bitnami.com/cluster-wide'] = 'true';
|
||||
}
|
||||
|
||||
// 5. Apply to the cluster
|
||||
// 6. Apply to the cluster
|
||||
await SealedSecret.apiEndpoint.post(sealedSecretData);
|
||||
|
||||
enqueueSnackbar('SealedSecret created successfully', { variant: 'success' });
|
||||
|
||||
Reference in New Issue
Block a user