
Research
/Security News
PolinRider Spreads Through Compromised GitHub Accounts and Packagist
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.
@egulatee/pulumi-spring-cloud-config
Advanced tools
Pulumi Dynamic Provider for integrating Spring Cloud Config Server with infrastructure-as-code projects
Pulumi Dynamic Provider for integrating Spring Cloud Config Server with infrastructure-as-code projects
This package provides a Pulumi Dynamic Provider that fetches configuration from Spring Cloud Config Server and makes it available to your infrastructure-as-code projects. It eliminates code duplication and provides standardized, secure configuration retrieval across your Pulumi stacks.
npm install @egulatee/pulumi-spring-cloud-config
import * as pulumi from '@pulumi/pulumi';
import { ConfigServerConfig } from '@egulatee/pulumi-spring-cloud-config';
const config = new pulumi.Config();
// Fetch configuration from Spring Cloud Config Server
const dbConfig = new ConfigServerConfig('database-config', {
configServerUrl: 'https://config-server.example.com',
application: 'my-service',
profile: pulumi.getStack(), // 'dev', 'staging', 'prod'
username: config.require('configServerUsername'),
password: config.requireSecret('configServerPassword'),
propertySources: ['vault'], // Optional: filter to Vault-only
});
// Get individual properties
const dbPassword = dbConfig.getProperty('database.password', true); // marked as secret
const dbHost = dbConfig.getProperty('database.host');
const dbPort = dbConfig.getProperty('database.port');
// Use in other resources
export const databaseUrl = pulumi.interpolate`postgresql://${dbHost}:${dbPort}`;
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
configServerUrl | string | Yes | - | The URL of the Spring Cloud Config Server |
application | string | Yes | - | The application name to fetch configuration for |
profile | string | Yes | - | The profile(s) to fetch configuration for (comma-separated) |
label | string | No | - | The label/branch to fetch configuration from |
username | string | No | - | Username for Basic Authentication |
password | string | No | - | Password for Basic Authentication |
propertySources | string[] | No | - | Filter property sources by name (e.g., ["vault"]) |
timeout | number | No | 10000 | Request timeout in milliseconds |
debug | boolean | No | false | Enable debug logging |
autoDetectSecrets | boolean | No | true | Automatically detect and mark secrets |
enforceHttps | boolean | No | false | Enforce HTTPS (fail on HTTP except localhost) |
This provider uses smart diffing to determine when to fetch configuration from the Spring Cloud Config Server:
Configuration is fetched from the config-server in these scenarios:
ConfigServerConfig resourceconfigServerUrlapplicationprofilelabelusernamepasswordpropertySourcesConfiguration is not fetched in these scenarios:
pulumi up without any changes to inputspulumi preview (read-only operation)If configuration changes on the config-server without changing your Pulumi code, use pulumi refresh:
# Explicitly fetch latest configuration from config-server
pulumi refresh
This will detect changes made directly on the config-server (e.g., rotated secrets, updated values).
# Normal deployments (only fetches if inputs changed)
pulumi up
# After rotating secrets on config-server
pulumi refresh # Detect upstream changes
pulumi up # Apply any resulting infrastructure changes
# Regular deployments
pulumi up
# Scheduled configuration sync (optional)
# Run this periodically to detect upstream changes
pulumi refresh && pulumi up
Smart Diffing (Current Approach)
Always Refresh (Alternative)
pulumi upAlways use HTTPS in production:
const config = new ConfigServerConfig('config', {
configServerUrl: 'https://config-server.example.com', // ✅ HTTPS
enforceHttps: true, // Fail if HTTP used (except localhost)
// ...
});
HTTP URLs will trigger warnings unless enforceHttps is explicitly set to false or the URL is localhost.
The provider automatically detects and marks secrets based on key patterns:
Detected Patterns:
password, passwd, pwdsecret, tokenapi_key, apikey, api-keyprivate_key, privatekeyaccess_key, accesskeyOverride Secret Detection:
// Disable auto-detection globally
const config = new ConfigServerConfig('config', {
autoDetectSecrets: false,
// ...
});
// Override per-property
const publicKey = dbConfig.getProperty('public_key', false); // NOT marked as secret
const apiKey = dbConfig.getProperty('api_key', true); // Force mark as secret
Store config-server credentials securely:
import * as pulumi from '@pulumi/pulumi';
const pulumiConfig = new pulumi.Config();
const config = new ConfigServerConfig('config', {
configServerUrl: pulumiConfig.require('configServerUrl'),
username: pulumiConfig.require('configServerUsername'),
password: pulumiConfig.requireSecret('configServerPassword'), // ✅ Encrypted
// ...
});
Set encrypted configuration:
pulumi config set configServerUsername admin
pulumi config set --secret configServerPassword 'your-password'
Fetch only from specific property sources (e.g., Vault):
const vaultConfig = new ConfigServerConfig('vault-config', {
configServerUrl: 'https://config-server.example.com',
application: 'my-service',
profile: 'prod',
propertySources: ['vault'], // Only fetch from Vault
username: config.require('configServerUsername'),
password: config.requireSecret('configServerPassword'),
});
// Get all properties from Vault sources
const allVaultProps = vaultConfig.getSourceProperties(['vault']);
Enable verbose logging for troubleshooting:
const config = new ConfigServerConfig('debug-config', {
configServerUrl: 'https://config-server.example.com',
application: 'my-service',
profile: 'dev',
debug: true, // ✅ Enable debug logging
});
Adjust timeout for slow config-servers:
const config = new ConfigServerConfig('slow-config', {
configServerUrl: 'https://slow-config-server.example.com',
application: 'my-service',
profile: 'prod',
timeout: 30000, // 30 seconds (default: 10 seconds)
});
┌─────────────────┐
│ Pulumi Program │
│ │
│ ConfigServer │
│ Config(...) │
└────────┬────────┘
│
▼
┌─────────────────────────┐
│ Dynamic Provider │
│ - Validates inputs │
│ - Fetches config │
│ - Detects secrets │
│ - Smart diffing │
└────────┬────────────────┘
│
▼
┌─────────────────────────┐
│ HTTP Client │
│ - Basic Auth │
│ - Retry logic │
│ - Error handling │
└────────┬────────────────┘
│
▼
┌─────────────────────────────┐
│ Spring Cloud Config Server │
│ ┌─────────────────────────┐ │
│ │ Property Sources: │ │
│ │ • Git Repository │ │
│ │ • HashiCorp Vault │ │
│ │ • Local Files │ │
│ │ • Environment Variables │ │
│ └─────────────────────────┘ │
└─────────────────────────────┘
For a detailed architecture diagram, see docs/architecture.txt.
Key Components:
Data Flow:
getProperty() and getSourceProperties()The provider handles various error scenarios gracefully:
| Status Code | Behavior | Retry? |
|---|---|---|
| 401 | Authentication failed - check username/password | ❌ No |
| 403 | Access forbidden - insufficient permissions | ❌ No |
| 404 | Configuration not found for application/profile | ❌ No |
| 500 | Config server internal error | ❌ No |
| 503 | Service unavailable | ✅ Yes (up to 3 times) |
| Error Type | Description | Retry? |
|---|---|---|
| ECONNREFUSED | Cannot connect to config server | ✅ Yes |
| ETIMEDOUT | Request timeout | ✅ Yes |
| ECONNABORTED | Connection aborted | ✅ Yes |
| ENOTFOUND | DNS resolution failed | ✅ Yes |
Example with retries:
const config = new ConfigServerConfig('config', {
configServerUrl: 'https://config-server.example.com',
application: 'my-app',
profile: 'prod',
timeout: 15000, // Allow more time for retries
});
All error messages are sanitized to remove credentials:
❌ Bad: "Failed to connect to https://user:password@config-server.example.com"
✅ Good: "Failed to connect to https://***:***@config-server.example.com"
new ConfigServerConfig(name: string, args: ConfigServerConfigArgs, opts?: pulumi.CustomResourceOptions)
Parameters:
name - Unique name for this resourceargs - Configuration arguments (see Configuration Options)opts - Optional Pulumi resource optionsThe full configuration response from the config server.
Type Definition:
interface ConfigServerResponse {
name: string; // Application name
profiles: string[]; // Active profiles
label: string | null; // Git label/branch
version: string | null; // Git commit hash
state: string | null; // State information
propertySources: PropertySource[]; // Array of property sources
}
interface PropertySource {
name: string; // Source identifier (e.g., "vault:/secret/app/prod")
source: Record<string, unknown>; // Key-value properties
}
All configuration properties flattened into a single key-value map. Later sources override earlier ones.
Get a single property value from the configuration.
Parameters:
key - The property key using dot notation (e.g., "database.password")markAsSecret (optional) - Override automatic secret detection:
true - Force mark as secretfalse - Prevent marking as secretundefined - Use automatic detection (default)Returns: pulumi.Output<string | undefined> - The property value, or undefined if not found
Examples:
// Auto-detect secrets
const dbPassword = config.getProperty("database.password"); // Marked as secret
// Force mark as secret
const apiKey = config.getProperty("api.endpoint", true);
// Prevent marking as secret
const publicKey = config.getProperty("rsa.publicKey", false);
Get properties from specific property sources.
Parameters:
sourceNames (optional) - Array of source name filters (case-insensitive substring match)
Returns: pulumi.Output<Record<string, unknown>> - Filtered properties map
Examples:
// Get all Vault properties
const vaultProps = config.getSourceProperties(["vault"]);
// Get properties from Vault OR Git sources
const vaultOrGit = config.getSourceProperties(["vault", "git"]);
// Get all properties (same as config.properties)
const allProps = config.getSourceProperties();
Source Name Matching:
vault:/secret/app/prod → Matches filter: ["vault"] ✅git:https://github.com/org/config → Matches filter: ["git"] ✅file:///config/application.yml → Matches filter: ["vault"] ❌Get all properties that were automatically detected as secrets.
Returns: pulumi.Output<Record<string, string>> - All auto-detected secrets
Note: Only works if autoDetectSecrets: true (default). Returns empty object if disabled.
Secret Detection Pattern:
/password|secret|token|.*key$|credential|auth|api[_-]?key/i
Examples:
const secrets = config.getAllSecrets();
// Use with AWS Secrets Manager
secrets.apply(secretMap => {
for (const [key, value] of Object.entries(secretMap)) {
new aws.secretsmanager.Secret(`${key}`, {
secretString: value,
});
}
});
If you're currently using custom HTTP client code to fetch configuration from Spring Cloud Config Server, here's how to migrate:
import * as pulumi from '@pulumi/pulumi';
import axios from 'axios';
// Manually fetch configuration
async function getConfig() {
const response = await axios.get(
'https://config-server.example.com/my-app/prod',
{
auth: {
username: 'admin',
password: 'secret',
},
}
);
// Manually flatten properties
const props: Record<string, any> = {};
for (const source of response.data.propertySources) {
Object.assign(props, source.source);
}
return props;
}
// Use in Pulumi program (problematic!)
const configPromise = getConfig();
export const dbPassword = configPromise.then(c => c['database.password']);
Problems with this approach:
pulumi up)import * as pulumi from '@pulumi/pulumi';
import { ConfigServerConfig } from '@egulatee/pulumi-spring-cloud-config';
const pulumiConfig = new pulumi.Config();
const config = new ConfigServerConfig('config', {
configServerUrl: 'https://config-server.example.com',
application: 'my-app',
profile: 'prod',
username: pulumiConfig.require('configServerUsername'),
password: pulumiConfig.requireSecret('configServerPassword'),
});
// Access properties with proper Pulumi Output handling
export const dbPassword = config.getProperty('database.password');
Benefits:
1. Install the package:
npm install @egulatee/pulumi-spring-cloud-config
2. Replace manual HTTP calls with ConfigServerConfig:
// Remove
import axios from 'axios';
// Add
import { ConfigServerConfig } from '@egulatee/pulumi-spring-cloud-config';
3. Store credentials in Pulumi config:
pulumi config set configServerUsername admin
pulumi config set --secret configServerPassword your-password
4. Replace config fetching logic:
// Remove manual fetching
const configData = await axios.get(...);
// Add resource
const config = new ConfigServerConfig('config', {
configServerUrl: 'https://config-server.example.com',
application: 'my-app',
profile: pulumi.getStack(),
username: pulumiConfig.require('configServerUsername'),
password: pulumiConfig.requireSecret('configServerPassword'),
});
5. Update property access:
// Replace direct property access
const dbHost = configData.properties['database.host'];
// With getProperty()
const dbHost = config.getProperty('database.host');
6. Test the migration:
pulumi preview
pulumi up
See the examples directory for complete, runnable examples:
Basic Usage - Simple configuration fetch
With Authentication - Security best practices
Vault-Only Configuration - Property source filtering
getSourceProperties() usagegetAllSecrets() demonstrationComplete AWS Infrastructure - Real-world deployment
Multi-Environment - Stack-based environments
All examples include:
See examples/README.md for quick start instructions.
This project uses semantic-release for automated version management and package publishing.
Releases happen automatically when commits are merged to the main branch. No manual intervention is required.
How it works:
mainpackage.json version is bumped automaticallyv0.1.0)Versions are determined by commit message types following Conventional Commits:
| Commit Type | Version Bump | Example |
|---|---|---|
fix: | PATCH | 0.1.0 → 0.1.1 |
feat: | MINOR | 0.1.0 → 0.2.0 |
BREAKING CHANGE: | MINOR (in 0.x) | 0.1.0 → 0.2.0 |
BREAKING CHANGE: | MAJOR (in 1.x+) | 1.0.0 → 2.0.0 |
Note: Breaking changes bump MINOR version in 0.x releases to signal instability. Once the package reaches 1.0.0, breaking changes will bump MAJOR version.
When contributing to this project:
Follow Conventional Commits - Your commit messages determine the release version
feat: add OAuth2 authentication support
fix: resolve timeout error in config fetch
docs: update README with new examples
No manual version bumping - Never edit package.json version manually
"version": "0.2.0"No manual CHANGELOG edits - CHANGELOG.md is auto-generated
View releases - Check GitHub Releases for published versions
Adding a feature (MINOR bump):
git commit -m "feat: add support for JWT authentication
Implements JWT token authentication for config server.
Allows users to authenticate using bearer tokens.
Closes #123"
Fixing a bug (PATCH bump):
git commit -m "fix: resolve timeout error in retry logic
The exponential backoff was not respecting max timeout.
Now correctly times out after configured duration.
Fixes #456"
Breaking change (MINOR in 0.x, MAJOR in 1.x+):
git commit -m "feat: redesign authentication API
BREAKING CHANGE: The authentication configuration has been
restructured. Users must migrate from 'username/password'
to 'auth: { type: "basic", credentials: {...} }'.
See migration guide for details.
Fixes #789"
# Clone the repository
git clone https://github.com/egulatee/pulumi-spring-cloud-config.git
cd pulumi-spring-cloud-config
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
npm run build - Compile TypeScript to JavaScriptnpm run clean - Remove build artifactsnpm test - Run testsnpm run test:watch - Run tests in watch modenpm run test:coverage - Run tests with coverage reportnpm run lint - Lint codenpm run lint:fix - Lint and auto-fix issuesnpm run format - Format code with Prettiernpm run format:check - Check code formattingSee CONTRIBUTING.md for development guidelines.
If configuration on the config-server changed but Pulumi doesn't detect it:
# Explicitly refresh to detect upstream changes
pulumi refresh
# Then apply
pulumi up
If requests are timing out:
const config = new ConfigServerConfig('config', {
// Increase timeout
timeout: 30000, // 30 seconds
// ...
});
To suppress HTTPS warnings for localhost development:
const config = new ConfigServerConfig('config', {
configServerUrl: 'http://localhost:8888', // Localhost is allowed
// ...
});
Or explicitly allow HTTP:
const config = new ConfigServerConfig('config', {
configServerUrl: 'http://config-server.internal', // Internal network
enforceHttps: false, // Disable HTTPS enforcement
// ...
});
Apache-2.0 - See LICENSE for details
Built with:
FAQs
Pulumi Dynamic Provider for integrating Spring Cloud Config Server with infrastructure-as-code projects
We found that @egulatee/pulumi-spring-cloud-config demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.

Company News
Allow myself to introduce... myself.