-
Notifications
You must be signed in to change notification settings - Fork 58
[DB-19] feat: secretsManager #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 33 commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
757ca49
init files
nafees87n 0f8cb7a
added external secrets manager interfaces
nafees87n 7b4d869
fix: config type
nafees87n fa080d0
added method
nafees87n 0caccc0
refactor
nafees87n a965650
refactor folder name
nafees87n e68e69f
Merge branch 'master' of github.com:requestly/requestly-desktop-app i…
nafees87n 2f38597
fix: interface reusability
nafees87n bbefd3f
fix
nafees87n a808ba9
added caching service
nafees87n aa26c49
refactored providerRegistry
nafees87n 13f5131
fix: types
nafees87n 2bb53a6
fix
nafees87n 36e1b45
poc code
nafees87n 3b34f7d
added initilization flow
nafees87n bdb67fb
remove unused code
nafees87n b597f38
Merge branch 'master' of github.com:requestly/requestly-desktop-app i…
nafees87n 6968919
fix
nafees87n 1756a21
fix: encrypted storage init
nafees87n e603899
fix: error
nafees87n 4375589
fix: type
nafees87n 0468284
fix: schemas
nafees87n c0b5a93
fix: config read/write
nafees87n 3faf236
fix: provider reading
nafees87n ae99164
added key sanitization
nafees87n d8b7462
fix: deletion result
nafees87n 9ae079b
fix: orphaned indexes in manifest
nafees87n 8de1756
use electron-store
nafees87n 56c2b4b
fix: initialization
nafees87n 0f93b01
fix: file name
nafees87n 11f1ede
fix: encryption code
nafees87n 8ff48a7
cleanup unncessary changes
nafees87n 2a4ca6d
Merge branch 'master' of github.com:requestly/requestly-desktop-app i…
nafees87n 04f496b
wrap in trycatch
nafees87n cbec03c
removed linting changes
nafees87n 555f2f0
Merge branch 'master' of github.com:requestly/requestly-desktop-app i…
nafees87n 63a10fc
fix: new line
nafees87n 88fb8fb
[DB-21] added variable fetching flow and awsSecretsManagerProvider (#…
nafees87n e95012d
convert into generic types
wrongsahil caaf624
fix: types
nafees87n 7bce339
readded vault types
nafees87n 20c9454
Merge branch 'master' of github.com:requestly/requestly-desktop-app i…
nafees87n 75cd56c
adds support for listener in secretsManager (#273)
nafees87n acf3121
[DB-29] added IPC methods for secretsManager (#277)
nafees87n 312b1f6
Merge branch 'master' of github.com:requestly/requestly-desktop-app i…
nafees87n File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
src/lib/secretsManager/encryptedStorage/AbstractSecretsManagerStorage.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { SecretProviderConfig } from "../types"; | ||
|
|
||
| export abstract class AbstractSecretsManagerStorage { | ||
| abstract set(_key: string, _data: SecretProviderConfig): Promise<void>; | ||
|
|
||
| abstract get(_key: string): Promise<SecretProviderConfig | null>; | ||
|
|
||
| abstract getAll(): Promise<SecretProviderConfig[]>; | ||
|
|
||
| abstract delete(_key: string): Promise<void>; | ||
| } |
29 changes: 29 additions & 0 deletions
29
src/lib/secretsManager/encryptedStorage/SecretsManagerEncryptedStorage.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { AbstractSecretsManagerStorage } from "./AbstractSecretsManagerStorage"; | ||
| import { EncryptedElectronStore } from "../../storage/EncryptedElectronStore"; | ||
| import { SecretProviderConfig } from "../types"; | ||
|
|
||
| export class SecretsManagerEncryptedStorage extends AbstractSecretsManagerStorage { | ||
| private encryptedStore: EncryptedElectronStore; | ||
|
|
||
| constructor(storeName: string) { | ||
| super(); | ||
| this.encryptedStore = new EncryptedElectronStore(storeName); | ||
| } | ||
|
|
||
| async set(key: string, data: SecretProviderConfig): Promise<void> { | ||
| return this.encryptedStore.set<SecretProviderConfig>(key, data); | ||
| } | ||
|
|
||
| async get(key: string): Promise<SecretProviderConfig | null> { | ||
| return this.encryptedStore.get<SecretProviderConfig>(key); | ||
| } | ||
|
|
||
| async getAll(): Promise<SecretProviderConfig[]> { | ||
| const allData = this.encryptedStore.getAll<SecretProviderConfig>(); | ||
| return Object.values(allData); | ||
| } | ||
|
|
||
| async delete(key: string): Promise<void> { | ||
| return this.encryptedStore.delete(key); | ||
| } | ||
| } |
25 changes: 25 additions & 0 deletions
25
src/lib/secretsManager/providerRegistry/AbstractProviderRegistry.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { SecretProviderConfig } from "../types"; | ||
| import { AbstractSecretsManagerStorage } from "../encryptedStorage/AbstractSecretsManagerStorage"; | ||
| import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; | ||
|
|
||
| export abstract class AbstractProviderRegistry { | ||
| protected store: AbstractSecretsManagerStorage; | ||
|
|
||
| protected providers: Map<string, AbstractSecretProvider> = new Map(); | ||
|
|
||
| constructor(store: AbstractSecretsManagerStorage) { | ||
| this.store = store; | ||
| } | ||
|
|
||
| abstract initialize(): Promise<void>; | ||
|
|
||
| abstract getAllProviderConfigs(): Promise<SecretProviderConfig[]>; | ||
|
|
||
| abstract getProviderConfig(_id: string): Promise<SecretProviderConfig | null>; | ||
|
|
||
| abstract setProviderConfig(_config: SecretProviderConfig): Promise<void>; | ||
|
|
||
| abstract deleteProviderConfig(_id: string): Promise<void>; | ||
|
|
||
| abstract getProvider(_providerId: string): AbstractSecretProvider | null; | ||
| } |
45 changes: 45 additions & 0 deletions
45
src/lib/secretsManager/providerRegistry/FileBasedProviderRegistry.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { SecretProviderConfig } from "../types"; | ||
| import { createProviderInstance } from "../providerService/providerFactory"; | ||
| import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider"; | ||
| import { AbstractProviderRegistry } from "./AbstractProviderRegistry"; | ||
|
|
||
| export class FileBasedProviderRegistry extends AbstractProviderRegistry { | ||
| async initialize(): Promise<void> { | ||
| await this.initProvidersFromStorage(); | ||
| } | ||
|
|
||
| private async initProvidersFromStorage(): Promise<void> { | ||
| const configs = await this.getAllProviderConfigs(); | ||
| configs.forEach((config) => { | ||
| this.providers.set(config.id, createProviderInstance(config)); | ||
| }); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| async getAllProviderConfigs(): Promise<SecretProviderConfig[]> { | ||
| const allConfigs = this.store.getAll(); | ||
| return allConfigs; | ||
| } | ||
|
|
||
| async getProviderConfig(id: string): Promise<SecretProviderConfig | null> { | ||
| try { | ||
| return await this.store.get(id); | ||
| } catch (error) { | ||
| console.error(`Failed to load provider config for id: ${id}`, error); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| async setProviderConfig(config: SecretProviderConfig): Promise<void> { | ||
| await this.store.set(config.id, config); | ||
| this.providers.set(config.id, createProviderInstance(config)); | ||
| } | ||
|
|
||
| async deleteProviderConfig(id: string): Promise<void> { | ||
| await this.store.delete(id); | ||
| this.providers.delete(id); | ||
| } | ||
|
|
||
| getProvider(providerId: string): AbstractSecretProvider | null { | ||
| return this.providers.get(providerId) ?? null; | ||
| } | ||
| } | ||
27 changes: 27 additions & 0 deletions
27
src/lib/secretsManager/providerService/AbstractSecretProvider.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { CachedSecret, ProviderSpecificConfig, SecretProviderType, SecretReference } from "../types"; | ||
|
|
||
| export abstract class AbstractSecretProvider { | ||
| protected cache: Map<string, CachedSecret> = new Map(); | ||
|
|
||
| abstract readonly type: SecretProviderType; | ||
|
|
||
| abstract readonly id: string; | ||
|
|
||
| protected config: ProviderSpecificConfig; | ||
|
|
||
| protected abstract getSecretIdentfier(ref: SecretReference): string; | ||
|
nafees87n marked this conversation as resolved.
Outdated
|
||
|
|
||
| abstract testConnection(): Promise<boolean>; | ||
|
|
||
| abstract getSecret(ref: SecretReference): Promise<string>; | ||
|
|
||
| abstract getSecrets(): Promise<string[]>; | ||
|
|
||
| abstract setSecret(): Promise<void>; | ||
|
|
||
| abstract setSecrets(): Promise<void>; | ||
|
nafees87n marked this conversation as resolved.
Outdated
|
||
|
|
||
| static validateConfig(config: any): boolean { | ||
| throw new Error("Not implemented"); | ||
| } | ||
| } | ||
4 changes: 4 additions & 0 deletions
4
src/lib/secretsManager/providerService/awsSecretManagerProvider.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| /* eslint-disable class-methods-use-this */ | ||
| import { AbstractSecretProvider } from "./AbstractSecretProvider"; | ||
|
|
||
| export class AWSSecretsManagerProvider extends AbstractSecretProvider {} | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { SecretProviderConfig, SecretProviderType } from "../types"; | ||
| import { AWSSecretsManagerProvider } from "./awsSecretManagerProvider"; | ||
| import { AbstractSecretProvider } from "./AbstractSecretProvider"; | ||
|
|
||
| export function createProviderInstance( | ||
| config: SecretProviderConfig | ||
| ): AbstractSecretProvider { | ||
| switch (config.type) { | ||
| case SecretProviderType.AWS_SECRETS_MANAGER: | ||
| return new AWSSecretsManagerProvider(config); | ||
| default: | ||
| throw new Error(`Unknown provider type: ${config.type}`); | ||
| } | ||
| } |
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initialization of class' singleton instance has been done in PR: #266 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { SecretProviderConfig } from "./types"; | ||
| import { AbstractProviderRegistry } from "./providerRegistry/AbstractProviderRegistry"; | ||
|
|
||
| export class SecretsManager { | ||
| private registry: AbstractProviderRegistry; | ||
|
|
||
| constructor(registry: AbstractProviderRegistry) { | ||
| this.registry = registry; | ||
| } | ||
|
|
||
| async initialize(): Promise<void> { | ||
| await this.registry.initialize(); | ||
| } | ||
|
|
||
| async addProviderConfig(config: SecretProviderConfig) { | ||
| console.log("!!!debug", "addconfig", config); | ||
| await this.registry.setProviderConfig(config); | ||
| } | ||
|
|
||
| async removeProviderConfig(id: string) { | ||
| await this.registry.deleteProviderConfig(id); | ||
| } | ||
|
|
||
| async getProviderConfig(id: string): Promise<SecretProviderConfig | null> { | ||
| return this.registry.getProviderConfig(id); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| export enum SecretProviderType { | ||
| AWS_SECRETS_MANAGER = "aws", | ||
| } | ||
|
|
||
| export interface AWSSecretsManagerConfig { | ||
| accessKeyId: string; | ||
| secretAccessKey: string; | ||
| region: string; | ||
| sessionToken?: string; | ||
| } | ||
|
nafees87n marked this conversation as resolved.
Outdated
nafees87n marked this conversation as resolved.
Outdated
|
||
|
|
||
| export type ProviderSpecificConfig = AWSSecretsManagerConfig; // | HashicorpVaultConfig | OtherProviderConfig; | ||
|
|
||
| export interface SecretProviderConfig { | ||
| id: string; | ||
| type: SecretProviderType; | ||
| name: string; | ||
| createdAt: number; | ||
| updatedAt: number; | ||
| config: ProviderSpecificConfig; | ||
| } | ||
|
|
||
| export type AwsSecretReference = { | ||
| type: SecretProviderType.AWS_SECRETS_MANAGER; | ||
| nameOrArn: string; | ||
| version?: string; | ||
| }; | ||
|
|
||
| export type SecretReference = AwsSecretReference; // | VaultSecretReference; // | OtherProviderSecretReference; | ||
|
|
||
| export interface CachedSecret { | ||
| id: string; // Unique identifier | ||
| identifier: string; // Secret identifier (name, ARN, or path) | ||
| value: string; // The actual secret value | ||
| providerId: string; | ||
| providerType: SecretProviderType; | ||
| fetchedAt: number; | ||
| expiresAt: number; | ||
| version?: string; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| import Store from "electron-store"; | ||
| import { safeStorage } from "electron"; | ||
|
|
||
| interface EncryptedStoreSchema { | ||
| version: number; | ||
| data: Record<string, any>; | ||
| } | ||
|
|
||
| const STORE_VERSION = 1; | ||
|
|
||
| /** | ||
| * Generic encrypted key-value storage using electron-store + Electron's safeStorage. | ||
| * - OS-level encryption via safeStorage (Keychain/DPAPI/libsecret) | ||
| * | ||
| * Storage location: <userData>/storage/<storeName>.txt | ||
| * | ||
| */ | ||
| export class EncryptedElectronStore { | ||
| private store: Store<EncryptedStoreSchema>; | ||
|
|
||
| constructor(storeName: string) { | ||
| if (!safeStorage.isEncryptionAvailable()) { | ||
| throw new Error( | ||
| "Encryption is not available on this system. Please ensure your operating system's secure storage is properly configured." | ||
| ); | ||
| } | ||
|
nafees87n marked this conversation as resolved.
|
||
|
|
||
| const storeOptions: Store.Options<EncryptedStoreSchema> = { | ||
| name: storeName, | ||
| cwd: "storage", | ||
| watch: true, | ||
| fileExtension: "txt", | ||
| serialize: (data) => { | ||
| const jsonString = JSON.stringify(data); | ||
| const encrypted = safeStorage.encryptString(jsonString); | ||
| const base64 = encrypted.toString("base64"); | ||
| return base64; | ||
| }, | ||
| deserialize: (data) => { | ||
| const encryptedBuffer = Buffer.from(data, "base64"); | ||
| const decrypted = safeStorage.decryptString(encryptedBuffer); | ||
| return JSON.parse(decrypted) as EncryptedStoreSchema; | ||
| }, | ||
| schema: { | ||
| version: { type: "number" }, | ||
| data: { type: "object", additionalProperties: true }, | ||
| }, | ||
| defaults: { | ||
| version: STORE_VERSION, | ||
| data: {}, | ||
| }, | ||
| }; | ||
|
|
||
| this.store = new Store<EncryptedStoreSchema>(storeOptions); | ||
| } | ||
|
|
||
| set<T>(key: string, data: T) { | ||
| this.store.set(`data.${key}`, data); | ||
| } | ||
|
|
||
| get<T>(key: string): T | null { | ||
| const data = this.store.get(`data.${key}`) as T; | ||
| return data ?? null; | ||
| } | ||
|
|
||
| getAll<T>(): T { | ||
| return this.store.get("data") as T; | ||
| } | ||
|
|
||
| delete(key: string): void { | ||
| this.store.delete(`data.${key}` as keyof EncryptedStoreSchema); | ||
| } | ||
|
|
||
| has(key: string): boolean { | ||
| return this.store.has(`data.${key}` as keyof EncryptedStoreSchema); | ||
| } | ||
|
|
||
| keys(): string[] { | ||
| return Object.keys(this.store.get("data")); | ||
| } | ||
|
|
||
| clear(): void { | ||
| this.store.set("data", {}); | ||
| } | ||
|
|
||
| /** | ||
| * Registers a callback for when storage changes. | ||
| * | ||
| * @param callback - Function to call when data changes | ||
| * @returns Unsubscribe function | ||
| */ | ||
| onChange(callback: (_data: Record<string, string>) => void): () => void { | ||
| return this.store.onDidChange("data", (newValue) => { | ||
| if (newValue) { | ||
| callback(newValue); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| getStore(): Store<EncryptedStoreSchema> { | ||
| return this.store; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.