npm package: @linode/kubeseal-encrypt
Description: Hybrid RSA-OAEP + AES-GCM encryption for Kubernetes Sealed Secrets in browser and Node.js
Author: Linode
Latest version: 1.0.1

# @linode/kubeseal-encrypt

Hybrid RSA-OAEP + AES-GCM encryption for Kubernetes Sealed Secrets, compatible with both browser and Node.js environments.

## Features

- ✅ Browser and Node.js support
- ✅ Compatible with Bitnami Sealed Secrets
- ✅ TypeScript with full type definitions
- ✅ Zero runtime dependencies
- ✅ ESM and CommonJS support
- ✅ PEM validation
- ✅ Tree-shakeable

## Installation

```bash
npm install @linode/kubeseal-encrypt
```

## Usage

### Browser

```typescript
import { encryptSecretItem } from '@linode/kubeseal-encrypt'

const publicKeyPEM = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----`

const encryptedData = await encryptSecretItem(
  publicKeyPEM,
  'default', // Kubernetes namespace
  'my-secret-value'
)

console.log(encryptedData)
```

### Node.js

```javascript
const { encryptSecretItem } = require('@linode/kubeseal-encrypt')
const fs = require('fs')

// Read PEM from file
const publicKeyPEM = fs.readFileSync('./sealed-secrets-cert.pem', 'utf8')

// Encrypt data
const encryptedData = await encryptSecretItem(
  publicKeyPEM,
  'default',
  'my-secret-value'
)

console.log(encryptedData)
```

### With Validation

```typescript
import { encryptSecretItem, validatePEM, validateNamespace } from '@linode/kubeseal-encrypt'

// Validate PEM before using
const pemValidation = validatePEM(publicKeyPEM)
if (!pemValidation.valid) {
  console.error('Invalid PEM:', pemValidation.error)
  process.exit(1)
}

// Validate namespace
const nsValidation = validateNamespace('my-namespace')
if (!nsValidation.valid) {
  console.error('Invalid namespace:', nsValidation.error)
  process.exit(1)
}

// Encrypt
const encrypted = await encryptSecretItem(publicKeyPEM, 'my-namespace', 'secret-value')
```

## API Reference

### `encryptSecretItem(sealedSecretsPEM, namespace, data)`

Encrypts data using the Sealed Secrets public key.

**Parameters:**
- `sealedSecretsPEM` (string): PEM-formatted RSA public key from sealed-secrets controller
- `namespace` (string): Kubernetes namespace (required)
- `data` (string): Data to encrypt

**Returns:** `Promise<string>` - Encrypted data

**Throws:**
- `Error` if PEM format is invalid
- `Error` if namespace is invalid
- `Error` if crypto API is unavailable

### `validatePEM(pem)`

Validates PEM format.

**Parameters:**
- `pem` (string): PEM-formatted public key

**Returns:** `ValidationResult` - Object with `valid` boolean and optional `error` message

### `validateNamespace(namespace)`

Validates Kubernetes namespace format.

**Parameters:**
- `namespace` (string): Kubernetes namespace to validate

**Returns:** `ValidationResult` - Object with `valid` boolean and optional `error` message

## How It Works

This package implements the same hybrid encryption scheme as [kubeseal](https://github.com/bitnami-labs/sealed-secrets/blob/main/README.md#kubeseal):

1. **RSA-OAEP**: Encrypts a random 256-bit AES session key using the sealed-secrets public key
2. **AES-GCM**: Encrypts the actual data with the session key
3. **Output format**: `[2-byte RSA length][RSA-encrypted key][AES-encrypted data][16-byte auth tag]`

The namespace is used as the RSA-OAEP label for additional security, ensuring that encrypted data can only be decrypted in the correct namespace.

## Getting the Public Key

The recommended way to get the sealed-secrets public certificate is using kubeseal:

### Method 1: Fetch from Controller (Recommended)

```bash
kubeseal --fetch-cert \
  --controller-name=sealed-secrets-controller \
  --controller-namespace=kube-system \
  > sealed-secrets-cert.pem
```
> [!NOTE]  
> Replace sealed-secrets-controller and kube-system with your actual controller name and namespace if different.

This fetches the certificate directly from the sealed-secrets controller running in your cluster.

### Method 2: Download from Kubernetes directly

The controller prints the certificate to its logs on startup. You can retrieve it with:

```bash
kubectl get secret -n kube-system sealed-secrets-key \
  -o jsonpath="{.data['public\.pem']}" | base64 -d > sealed-secrets-cert.pem
```
> [!NOTE]  
> Replace the namespace and secret name if they are different in your setup.


### Notes

- The certificate automatically renews every 30 days (since v0.9.x)
- For offline/air-gapped environments, save the certificate locally and distribute it
- You can set the `SEALED_SECRETS_CERT` environment variable to point to a local certificate file

## Requirements

- **Browser**: Modern browser with Web Crypto API support
- **Node.js**: Version 18.0.0 or higher

## TypeScript

This package includes full TypeScript type definitions:

```typescript
import type { ValidationResult, CryptoAdapter, AlgorithmConfig } from '@linode/kubeseal-encrypt'
```

## Development

```bash
# Install dependencies
npm install

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Build package
npm run build

# Create local tarball for testing
npm run pack:local
```

## Testing Locally

### In Another Project

1. Build and pack the package:
   ```bash
   npm run build
   npm pack
   ```

2. Install in your project:
   ```bash
   npm install /path/to/linode-kubeseal-encrypt-1.0.0.tgz
   ```

3. Use it:
   ```javascript
   const { encryptSecretItem } = require('@linode/kubeseal-encrypt')
   ```

### In Kubernetes Cluster

1. Get the sealed-secrets public key:
   ```bash
   kubeseal --fetch-cert \
     --controller-name=sealed-secrets-controller \
     --controller-namespace=kube-system \
     > sealed-secrets-cert.pem
   ```
   > [!NOTE]  
   > Replace sealed-secrets-controller and kube-system with your actual controller name and namespace if different.

2. Create a test script:
   ```javascript
   const { encryptSecretItem } = require('@linode/kubeseal-encrypt')
   const fs = require('fs')

   ;(async () => {
     const pem = fs.readFileSync('public-key.pem', 'utf8')
     const encrypted = await encryptSecretItem(pem, 'default', 'test-secret')
     console.log('Encrypted:', encrypted)
   })()
   ```

3. Create a SealedSecret manifest:
   ```yaml
   apiVersion: bitnami.com/v1alpha1
   kind: SealedSecret
   metadata:
     name: my-sealed-secret
     namespace: default
   spec:
     encryptedData:
       password: <encrypted-data-from-script>
   ```

4. Apply and verify:
   ```bash
   kubectl apply -f sealed-secret.yaml
   kubectl get secret my-sealed-secret -o yaml
   ```

## License

Copyright 2025 Akamai Technologies, Inc.

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.

## Contributing

We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

For security issues, please see [SECURITY.md](SECURITY.md).

## Privacy

For information about Akamai's privacy practices, please visit https://www.akamai.com/legal/privacy-statement