Why centralized secret management is necessary
In modern software development, especially in containerized and collaborative environments, centralized secret management has become increasingly important. Here are
Flexibility in containerized deployment
- Real-time environment variable updates: In containerized deployments, centralized secret management allows us to easily update environment variables without rebuilding or redeploying containers. This greatly improves system flexibility and security.
- Environment consistency: It ensures all container instances use the same up-to-date secrets, reducing problems caused by environment inconsistencies.
Convenience in multi-developer scenarios
- Avoiding
.env
file transfers: Traditionally, developers might need to send.env
files via email or messaging apps, which is not only insecure but can also lead to version confusion. - Permission management: Centralized management allows us to set different access permissions for different team members, enhancing security.
- Version control: You can track the change history of secrets, making audits and rollbacks easier. two main reasons:
A little about Infisical
Infisical is a secret management service similar to HashiCorp Vault, but it focuses more on the developer experience.
Advantages of Infisical
- User-friendly: Offers an intuitive web interface and CLI tools, making secret management simple.
- Integration with development workflows: Provides SDKs in multiple languages, making it easy to integrate into existing projects.
- Team collaboration: Supports secure sharing and management of secrets among team members.
Paid features
- Advanced audit logs
- Custom roles and more granular permission controls
- SAML single sign-on
- Advanced key rotation strategies
Writing a NestJS Module to integrate Infisical
First, install the necessary dependency:
npm install @infisical/sdk
Then, create a new infisical.module.ts
import { DynamicModule, Global, Module } from '@nestjs/common'; import { InfisicalClient } from '@infisical/sdk'; import { InfisicalService } from './infisical.service'; import { InfisicalModuleOptions } from './infisical-module-options.type'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Global() @Module({}) export class InfisicalModule { static forRoot(options: InfisicalModuleOptions): DynamicModule { return { imports: [ // fallback to dotenv ConfigModule.forRoot({ envFilePath: options.fallbackFile, }), ], module: InfisicalModule, providers: [ { provide: 'INFISICAL_OPTIONS', useValue: { ...options }, }, { provide: InfisicalClient, useFactory: (config: ConfigService) => { return new InfisicalClient({ siteUrl: config.get<string>('INFISICAL_SITE_URL'), auth: { universalAuth: { clientId: config.get<string>('INFISICAL_CLIENT_ID', ''), clientSecret: config.get<string>('INFISICAL_CLIENT_SECRET', ''), }, }, }); }, inject: [ConfigService], }, InfisicalService, ], exports: [InfisicalService], }; } }
The infisical.service.ts
import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { InfisicalClient } from '@infisical/sdk'; import { InfisicalModuleOptions } from './infisical-module-options.type'; @Injectable() export class InfisicalService implements OnModuleInit { private logger = new Logger(InfisicalService.name); private fallbackToConfig = false; private secrets: Record<string, string | boolean | undefined> = {}; private readonly initializationPromise: Promise<void>; private readonly PROCESS_ENVS: string[] = [ 'DATABASE_URL', 'GOOGLE_APPLICATION_CREDENTIALS', ]; constructor( private readonly config: ConfigService, private readonly client: InfisicalClient, @Inject('INFISICAL_OPTIONS') private readonly options: InfisicalModuleOptions, ) { this.initializationPromise = this.init(); } async onModuleInit() { await this.initializationPromise; } private async init() { if (!this.config.get<string>('INFISICAL_SITE_URL')) { this.logger.log('Use config from ConfigService'); this.fallbackToConfig = true; return; } try { const secrets = await this.client.listSecrets({ environment: this.config.get<string>('INFISICAL_ENV', ''), projectId: this.config.get<string>('INFISICAL_PROJECT_ID', ''), path: this.options.path || '/', // path to infisical project's path includeImports: true, }); secrets.forEach(secret => { this.secrets[secret.secretKey] = secret.secretValue; if (this.PROCESS_ENVS.includes(secret.secretKey)) { // ENVs where should load directly into process // like prisma's DATABASE_URL & google cloud credential process.env[secret.secretKey] = secret.secretValue; } }); this.logger.log('Secrets loaded from Infisical'); } catch (error) { this.logger.warn( 'Failed to fetch secrets from Infisical, falling back to ConfigService', ); this.fallbackToConfig = true; } } public get<T = string>(key: string): T { if (this.fallbackToConfig) { return this.config.get<T>(key) as T; } if (Object.keys(this.secrets).length > 0) { return this.secrets[key] as T; } const value = this.secrets[key]; if (value === undefined) { return this.config.get<T>(key) as T; } return value as T; } }
The infisical-module-options.type
export type InfisicalModuleOptions = { path?: string; fallbackFile?: string | string[]; };
Use it
Write env in your dotenv
INFISICAL_ENV=dev # the slot of environments INFISICAL_PROJECT_ID=<your-infisical-project-id> INFISICAL_SITE_URL=<your-infisical-site-url> INFISICAL_CLIENT_ID=<your-infisical-client-id> INFISICAL_CLIENT_SECRET=<your-infisical-client-secret>
And import it into your app.module.ts
@Module({ imports: [InfisicalModule.forRoot({path: '/'})] })
Then, you can use it as ConfigService
of nestjs
infisicalService.get<string>('YOUR_ENV_SETUP_IN_INFISICAL')
That is
Top comments (0)