@voidkey/broker-core
Version:
Core credential minting logic for the voidkey zero-trust credential broker
331 lines • 14.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CredentialBroker = exports.createProvider = exports.AccessProvider = exports.IdpConfigLoader = exports.IdpProvider = void 0;
const idp_1 = require("./idp");
const idp_config_1 = require("./config/idp-config");
const providers_1 = require("./providers");
var idp_2 = require("./idp");
Object.defineProperty(exports, "IdpProvider", { enumerable: true, get: function () { return idp_2.IdpProvider; } });
var idp_config_2 = require("./config/idp-config");
Object.defineProperty(exports, "IdpConfigLoader", { enumerable: true, get: function () { return idp_config_2.IdpConfigLoader; } });
var providers_2 = require("./providers");
Object.defineProperty(exports, "AccessProvider", { enumerable: true, get: function () { return providers_2.AccessProvider; } });
Object.defineProperty(exports, "createProvider", { enumerable: true, get: function () { return providers_2.createProvider; } });
class CredentialBroker {
constructor() {
this.idpProviders = new Map();
this.accessProviders = new Map();
this.clientIdentities = new Map();
// Add built-in hello-world provider for testing/demo purposes
this.addBuiltInProviders();
}
addBuiltInProviders() {
// Add hello-world provider for testing/demo purposes
const helloWorldProvider = new idp_1.HelloWorldProvider();
this.addIdpProvider(helloWorldProvider);
// Set hello-world as default until user configures real IdPs
this.setDefaultIdp('hello-world');
}
addIdpProvider(provider) {
this.idpProviders.set(provider.getName(), provider);
}
addAccessProvider(provider) {
this.accessProviders.set(provider.getName(), provider);
}
getAccessProvider(name) {
const provider = this.accessProviders.get(name);
if (!provider) {
throw new Error(`Access provider '${name}' not found`);
}
return provider;
}
listAccessProviders() {
const providers = [];
for (const [name, provider] of this.accessProviders) {
providers.push({
name,
type: provider.getType()
});
}
return providers.sort((a, b) => a.name.localeCompare(b.name));
}
loadIdpConfigFromFile(configPath) {
const config = idp_config_1.IdpConfigLoader.loadFromFile(configPath);
this.loadIdpConfig(config);
}
loadIdpConfigFromString(yamlContent) {
const config = idp_config_1.IdpConfigLoader.loadFromString(yamlContent);
this.loadIdpConfig(config);
}
loadIdpConfig(config) {
// Broker IdP configuration is mandatory for OIDC authentication
if (!config.brokerIdp) {
throw new Error('Broker IdP configuration is required. The broker must authenticate with its own IdP to mint credentials.');
}
console.log(`✅ Loading broker IdP: ${config.brokerIdp.name}`);
this.brokerIdpConfig = config.brokerIdp;
// Handle client IdPs configuration
const clientIdps = config.clientIdps || [];
// Add all client IdP providers from config
clientIdps.forEach((idpConfig) => {
const provider = new idp_1.CustomIdpProvider(idpConfig);
this.addIdpProvider(provider);
});
// Load access providers for credential minting
if (config.accessProviders) {
console.log(`Loading ${config.accessProviders.length} access providers`);
config.accessProviders.forEach(providerConfig => {
try {
const provider = (0, providers_1.createProvider)(providerConfig);
this.addAccessProvider(provider);
console.log(`✅ Loaded access provider: ${provider.getName()} (${provider.getType()})`);
}
catch (error) {
console.error(`❌ Failed to load access provider ${providerConfig.name}:`, error);
}
});
}
// Load client identities
if (config.clientIdentities) {
console.log(`Loading ${config.clientIdentities.length} client identities`);
config.clientIdentities.forEach(identity => {
this.clientIdentities.set(identity.subject, {
idp: identity.idp,
keys: identity.keys
});
});
}
// Set default IdP if specified
if (config.default) {
this.setDefaultIdp(config.default);
}
}
setDefaultIdp(name) {
if (this.idpProviders.has(name)) {
this.defaultIdpName = name;
}
else {
throw new Error(`IdP provider '${name}' not found`);
}
}
listIdpProviders() {
const providers = [];
for (const [name, provider] of this.idpProviders) {
providers.push({
name,
isDefault: name === this.defaultIdpName
});
}
return providers.sort((a, b) => a.name.localeCompare(b.name));
}
// Key-based methods
getAvailableKeys(subject) {
const identityConfig = this.clientIdentities.get(subject);
if (!identityConfig?.keys) {
return [];
}
return Object.keys(identityConfig.keys);
}
getKeyConfiguration(subject, keyName) {
const identityConfig = this.clientIdentities.get(subject);
if (!identityConfig?.keys) {
return null;
}
return identityConfig.keys[keyName] || null;
}
getIdpProvider(name) {
const providerName = name || this.defaultIdpName;
if (!providerName) {
throw new Error('No default IdP provider configured');
}
const provider = this.idpProviders.get(providerName);
if (!provider) {
throw new Error(`IdP provider '${providerName}' not found`);
}
return provider;
}
// Key-based credential minting
async mintKey(oidcToken, keyName, idpName, duration) {
try {
// Validate the OIDC token first
const idpProvider = this.getIdpProvider(idpName);
const claims = await idpProvider.validateToken(oidcToken);
console.log('🎉 Token validated for subject:', claims.sub);
console.log('🔍 Using IdP:', idpProvider.getName());
// Special handling for hello-world provider (demo/testing)
if (idpProvider.getName() === 'hello-world') {
console.log('✅ Using hello-world provider - bypassing key configuration checks');
// Return mock credentials for hello-world
return {
credentials: {
'HELLO_ACCESS_KEY': 'AKIAIOSFODNN7EXAMPLE',
'HELLO_SECRET_KEY': 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
'HELLO_SESSION_TOKEN': 'hello-world-session-token'
},
expiresAt: new Date(Date.now() + 3600000).toISOString(),
metadata: {
provider: 'hello-world',
keyName: keyName
}
};
}
// Check if the identity is configured
const identityConfig = this.clientIdentities.get(claims.sub);
if (!identityConfig) {
throw new Error(`Identity '${claims.sub}' is not configured`);
}
// Verify the identity is configured for the correct IdP
if (identityConfig.idp !== idpProvider.getName()) {
throw new Error(`Identity '${claims.sub}' is configured for IdP '${identityConfig.idp}', but token was validated by '${idpProvider.getName()}'`);
}
console.log('✅ Identity is configured:', claims.sub);
// Get the key configuration
const keyConfig = identityConfig.keys[keyName];
if (!keyConfig) {
const availableKeys = Object.keys(identityConfig.keys);
throw new Error(`Key "${keyName}" not found for identity "${claims.sub}". Available keys: ${availableKeys.join(', ')}`);
}
console.log(`🔑 Found key configuration for "${keyName}"`);
console.log(`🎯 Provider: ${keyConfig.provider}`);
// Override duration if provided
if (duration) {
keyConfig.duration = duration;
}
// Get the access provider
const accessProvider = this.getAccessProvider(keyConfig.provider);
// Ensure we have a valid broker token for authentication
const brokerToken = await this.ensureValidBrokerToken();
// Create the credential request
const request = {
subject: claims.sub,
keyName: keyName,
keyConfig: keyConfig,
brokerToken: brokerToken,
claims: claims
};
// Mint the credential using the access provider
console.log(`🔄 Minting credential via ${accessProvider.getName()} (${accessProvider.getType()})`);
const response = await accessProvider.mintCredential(request);
console.log(`✅ Successfully minted credential for key "${keyName}"`);
console.log(`📊 Credential expires at: ${response.expiresAt}`);
return response;
}
catch (error) {
console.error('❌ Key minting failed:', error);
throw new Error(`Key minting failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
// Mint multiple keys at once
async mintKeys(oidcToken, keyNames, idpName, duration) {
const results = {};
const errors = {};
console.log(`🔄 Minting ${keyNames.length} keys: ${keyNames.join(', ')}`);
for (const keyName of keyNames) {
try {
results[keyName] = await this.mintKey(oidcToken, keyName, idpName, duration);
}
catch (error) {
errors[keyName] = error instanceof Error ? error.message : String(error);
console.error(`❌ Failed to mint key "${keyName}":`, error);
}
}
if (Object.keys(errors).length > 0) {
console.warn(`⚠️ Some keys failed to mint:`, errors);
}
console.log(`✅ Successfully minted ${Object.keys(results).length}/${keyNames.length} keys`);
return results;
}
async healthCheckIdpProvider(idpName) {
try {
const idpProvider = this.getIdpProvider(idpName);
const providerName = idpProvider.getName();
if (idpProvider.healthCheck) {
const healthy = await idpProvider.healthCheck();
return { provider: providerName, healthy };
}
else {
// If no health check method, assume healthy
return { provider: providerName, healthy: true };
}
}
catch (error) {
const providerName = idpName || this.defaultIdpName || 'unknown';
return {
provider: providerName,
healthy: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
async healthCheckAllProviders() {
const results = [];
for (const [name, provider] of this.idpProviders) {
try {
if (provider.healthCheck) {
const healthy = await provider.healthCheck();
results.push({ provider: name, healthy });
}
else {
results.push({ provider: name, healthy: true });
}
}
catch (error) {
results.push({
provider: name,
healthy: false,
error: error instanceof Error ? error.message : String(error)
});
}
}
return results;
}
// Broker OIDC token management
async acquireBrokerToken() {
if (!this.brokerIdpConfig) {
throw new Error('Broker IdP configuration not loaded');
}
console.log(`🔐 Acquiring broker token from ${this.brokerIdpConfig.name}`);
const tokenRequest = {
grant_type: 'client_credentials',
client_id: this.brokerIdpConfig.clientId,
client_secret: this.brokerIdpConfig.clientSecret,
audience: this.brokerIdpConfig.audience
};
const fetch = require('node-fetch');
const response = await fetch(this.brokerIdpConfig.tokenEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(tokenRequest).toString()
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to acquire broker token: ${response.status} ${errorText}`);
}
const tokenData = await response.json();
if (!tokenData.access_token) {
throw new Error('No access_token in broker token response');
}
// Store token and calculate expiry (with 30 second buffer)
this.brokerToken = tokenData.access_token;
const expiresIn = tokenData.expires_in || 3600; // Default to 1 hour
this.brokerTokenExpiry = Date.now() + (expiresIn - 30) * 1000;
console.log(`✅ Broker token acquired, expires in ${expiresIn} seconds`);
return this.brokerToken; // We know it's not undefined since we just set it
}
async ensureValidBrokerToken() {
// Check if we have a token and it's not expired
if (this.brokerToken && this.brokerTokenExpiry && Date.now() < this.brokerTokenExpiry) {
return this.brokerToken;
}
// Token is missing or expired, acquire a new one
console.log('🔄 Broker token missing or expired, acquiring new token');
return await this.acquireBrokerToken();
}
async getBrokerToken() {
return await this.ensureValidBrokerToken();
}
}
exports.CredentialBroker = CredentialBroker;
//# sourceMappingURL=index.js.map