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 
.envfile transfers: Traditionally, developers might need to send.envfiles 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)