Initial release: Headlamp Sealed Secrets plugin v0.1.0
Features: - Complete SealedSecret CRD integration with Headlamp - Client-side encryption using controller's public key - Support for all three scoping modes (strict, namespace-wide, cluster-wide) - List and detail views for SealedSecrets - Encryption dialog for creating new SealedSecrets - Decryption support with RBAC awareness - Sealing keys management - Settings page for controller configuration - Integration with Secret detail view Technical: - Full TypeScript with strict mode - ~1,345 lines of code - Build size: 339.42 kB (93.21 kB gzipped) - Compatible with Headlamp v0.13.0+ - Apache 2.0 license Security: - All encryption performed client-side - RSA-OAEP + AES-256-GCM (kubeseal-compatible) - Auto-hide decrypted values after 30 seconds Closes: Initial implementation
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* SealedSecrets List View
|
||||
*
|
||||
* Displays all SealedSecrets in the cluster with filtering and navigation
|
||||
*/
|
||||
|
||||
import { Link } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import {
|
||||
SectionBox,
|
||||
SectionFilterHeader,
|
||||
SimpleTable,
|
||||
StatusLabel,
|
||||
} from '@kinvolk/headlamp-plugin/lib/CommonComponents';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { SealedSecret } from '../lib/SealedSecretCRD';
|
||||
import { SealedSecretScope } from '../types';
|
||||
import { EncryptDialog } from './EncryptDialog';
|
||||
|
||||
/**
|
||||
* Format scope for display
|
||||
*/
|
||||
function formatScope(scope: SealedSecretScope): string {
|
||||
switch (scope) {
|
||||
case 'strict':
|
||||
return 'Strict';
|
||||
case 'namespace-wide':
|
||||
return 'Namespace-wide';
|
||||
case 'cluster-wide':
|
||||
return 'Cluster-wide';
|
||||
default:
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SealedSecrets list view component
|
||||
*/
|
||||
export function SealedSecretList() {
|
||||
const [sealedSecrets, error] = SealedSecret.useList();
|
||||
const [createDialogOpen, setCreateDialogOpen] = React.useState(false);
|
||||
|
||||
// Show error if CRD is not installed
|
||||
if (error) {
|
||||
return (
|
||||
<SectionBox
|
||||
title="Sealed Secrets"
|
||||
>
|
||||
<Box p={2}>
|
||||
<StatusLabel status="error">Error</StatusLabel>
|
||||
<Box mt={2}>
|
||||
{error.message.includes('404') ? (
|
||||
<>
|
||||
<p>
|
||||
Sealed Secrets CRD not found. Please ensure Sealed Secrets is installed on your
|
||||
cluster.
|
||||
</p>
|
||||
<p>
|
||||
Install with: <code>kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml</code>
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<p>Failed to load Sealed Secrets: {error.message}</p>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</SectionBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionBox
|
||||
title="Sealed Secrets"
|
||||
>
|
||||
<SectionFilterHeader
|
||||
title=""
|
||||
noNamespaceFilter={false}
|
||||
actions={[
|
||||
<Button
|
||||
key="create"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => setCreateDialogOpen(true)}
|
||||
>
|
||||
Create Sealed Secret
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
<SimpleTable
|
||||
data={sealedSecrets}
|
||||
columns={[
|
||||
{
|
||||
label: 'Name',
|
||||
getter: (ss: SealedSecret) => (
|
||||
<Link
|
||||
routeName="sealedsecret"
|
||||
params={{
|
||||
namespace: ss.metadata.namespace,
|
||||
name: ss.metadata.name,
|
||||
}}
|
||||
>
|
||||
{ss.metadata.name}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Namespace',
|
||||
getter: (ss: SealedSecret) => ss.metadata.namespace,
|
||||
},
|
||||
{
|
||||
label: 'Encrypted Keys',
|
||||
getter: (ss: SealedSecret) => ss.encryptedKeysCount,
|
||||
},
|
||||
{
|
||||
label: 'Scope',
|
||||
getter: (ss: SealedSecret) => formatScope(ss.scope),
|
||||
},
|
||||
{
|
||||
label: 'Sync Status',
|
||||
getter: (ss: SealedSecret) => (
|
||||
<StatusLabel status={ss.isSynced ? 'success' : 'error'}>
|
||||
{ss.isSynced ? 'Synced' : 'Not Synced'}
|
||||
</StatusLabel>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Age',
|
||||
getter: (ss: SealedSecret) => ss.getAge(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</SectionBox>
|
||||
|
||||
<EncryptDialog
|
||||
open={createDialogOpen}
|
||||
onClose={() => setCreateDialogOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user