@voidkey/broker-core
Version:
Core credential minting logic for the voidkey zero-trust credential broker
251 lines โข 12.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.MinIOProvider = void 0;
const base_1 = require("./base");
const jwt = __importStar(require("jsonwebtoken"));
class MinIOProvider extends base_1.AccessProvider {
constructor(config) {
super(config);
this.config = config;
}
getName() {
return this.config.name;
}
getType() {
return 'minio-sts';
}
async mintCredential(request) {
this.validateKeyConfig(request.keyConfig);
// Broker OIDC token validation is mandatory
if (!request.brokerToken) {
throw new Error('Broker OIDC token is required for MinIO credential minting');
}
await this.validateBrokerToken(request.brokerToken);
const keyConfig = request.keyConfig;
const duration = keyConfig.duration || this.getDefaultDuration();
console.log(`๐ MinIO Provider: Minting credentials for key "${request.keyName}" (subject: ${request.subject})`);
if (!keyConfig.role) {
throw new Error(`MinIO key configuration must specify a role for key "${request.keyName}"`);
}
try {
// Use MinIO STS AssumeRoleWithWebIdentity to get temporary credentials
const stsResult = await this.createServiceAccount(request.brokerToken, // Pass the broker's OIDC token
'', // Not used anymore
keyConfig.role, duration);
console.log(`โ
MinIO Provider: Got STS credentials with role ${keyConfig.role}`);
// Map the STS response to the configured output environment variables
const credentials = {};
for (const [envVar, fieldPath] of Object.entries(keyConfig.outputs)) {
switch (fieldPath) {
case 'AccessKeyId':
credentials[envVar] = stsResult.accessKey;
break;
case 'SecretAccessKey':
credentials[envVar] = stsResult.secretKey;
break;
case 'SessionToken':
credentials[envVar] = stsResult.sessionToken || '';
break;
case 'Expiration':
credentials[envVar] = stsResult.expiration || new Date(Date.now() + (duration * 1000)).toISOString();
break;
case 'Endpoint':
credentials[envVar] = this.config.endpoint;
break;
default:
console.warn(`โ ๏ธ MinIO Provider: Unknown field path "${fieldPath}" for env var "${envVar}"`);
}
}
return {
credentials,
expiresAt: stsResult.expiration || new Date(Date.now() + (duration * 1000)).toISOString(),
metadata: {
provider: this.getName(),
keyName: request.keyName,
tempAccessKey: stsResult.accessKey,
role: keyConfig.role
}
};
}
catch (error) {
console.error(`โ MinIO Provider: Failed to mint credentials for key "${request.keyName}":`, error);
throw new Error(`MinIO credential minting failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
async healthCheck() {
try {
// Simple health check - verify we can reach MinIO endpoint
const fetch = require('node-fetch');
const response = await fetch(`${this.config.endpoint}/minio/health/live`);
return response.ok;
}
catch (error) {
console.error('MinIO health check failed:', error);
return false;
}
}
async createServiceAccount(accessKey, secretKey, policy, duration) {
try {
// Use MinIO STS AssumeRoleWithWebIdentity to get temporary credentials using OIDC token
console.log(`๐ง MinIO Provider: Creating temporary credentials via STS AssumeRoleWithWebIdentity`);
const actualDuration = duration || this.getDefaultDuration();
// MinIO STS endpoint format
const stsEndpoint = `${this.config.endpoint}`;
// Prepare the STS request parameters
const params = new URLSearchParams({
'Action': 'AssumeRoleWithWebIdentity',
'Version': '2011-06-15',
'DurationSeconds': actualDuration.toString(),
'WebIdentityToken': accessKey, // Pass the broker's OIDC token as WebIdentityToken
// No inline policy - MinIO will use the role/policy configured for the OIDC token
});
// If MinIO is configured with a role ARN, include it
if (this.config.roleArn) {
params.append('RoleArn', this.config.roleArn);
}
console.log(`๐ง MinIO STS Request URL: ${stsEndpoint}?${params.toString()}`);
console.log(`๐ง MinIO STS Authorization: Bearer ${accessKey.substring(0, 20)}...`);
const fetch = require('node-fetch');
const response = await fetch(`${stsEndpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params.toString() // Send parameters in body for POST request
});
if (!response.ok) {
const errorText = await response.text();
console.error(`โ MinIO STS error: ${response.status} - ${errorText}`);
throw new Error(`MinIO STS request failed: ${response.status} ${response.statusText}`);
}
const responseText = await response.text();
console.log(`โ
MinIO Provider: Received STS response`);
// Parse the XML response
const credentials = await this.parseSTSResponse(responseText);
return {
accessKey: credentials.AccessKeyId,
secretKey: credentials.SecretAccessKey,
sessionToken: credentials.SessionToken,
expiration: credentials.Expiration,
policy
};
}
catch (error) {
console.error('Failed to create MinIO temporary credentials:', error);
throw new Error(`Credential creation failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
generateAWSSignature(method, path, body) {
// Generate AWS Signature V4 for MinIO STS request
// This method is no longer used with OIDC-only authentication
// In a full implementation, this would use the broker's OIDC token for MinIO API calls
throw new Error('Legacy AWS signature generation is not supported with OIDC-only authentication');
}
async parseSTSResponse(responseText) {
// Parse the XML response from MinIO STS
// MinIO STS returns XML similar to AWS STS
// Simple XML parsing - in production, use a proper XML parser
const accessKeyMatch = responseText.match(/<AccessKeyId>(.*?)<\/AccessKeyId>/);
const secretKeyMatch = responseText.match(/<SecretAccessKey>(.*?)<\/SecretAccessKey>/);
const sessionTokenMatch = responseText.match(/<SessionToken>(.*?)<\/SessionToken>/);
const expirationMatch = responseText.match(/<Expiration>(.*?)<\/Expiration>/);
if (!accessKeyMatch || !secretKeyMatch) {
throw new Error('Invalid STS response: missing credentials');
}
return {
AccessKeyId: accessKeyMatch[1],
SecretAccessKey: secretKeyMatch[1],
SessionToken: sessionTokenMatch ? sessionTokenMatch[1] : undefined,
Expiration: expirationMatch ? expirationMatch[1] : undefined
};
}
// Method to cleanup service account (for proper credential lifecycle)
async cleanupServiceAccount(accessKey) {
// Note: STS temporary credentials automatically expire and don't need manual cleanup
// This method is kept for compatibility but no action is needed
console.log(`๐งน MinIO Provider: STS credentials for ${accessKey} will auto-expire (no cleanup needed)`);
}
async validateBrokerToken(token) {
try {
console.log('๐ MinIO Provider: Validating broker OIDC token');
// Decode token to get header and payload
const decoded = jwt.decode(token, { complete: true });
if (!decoded || typeof decoded === 'string') {
throw new Error('Invalid JWT token format');
}
const payload = decoded.payload;
// Validate basic JWT structure - aud is optional since some IdPs don't include it
if (!payload.iss || !payload.exp) {
throw new Error('JWT missing required claims (iss, exp)');
}
// Check expiration
if (Date.now() >= payload.exp * 1000) {
throw new Error('Broker token has expired');
}
// Validate issuer (required)
if (payload.iss !== this.config.brokerAuth.expectedIssuer) {
throw new Error(`Invalid issuer: expected ${this.config.brokerAuth.expectedIssuer}, got ${payload.iss}`);
}
// Validate audience (optional - only if present in token)
if (payload.aud) {
const tokenAudience = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
if (!tokenAudience.includes(this.config.brokerAuth.expectedAudience)) {
throw new Error(`Invalid audience: expected ${this.config.brokerAuth.expectedAudience}, got ${tokenAudience.join(', ')}`);
}
console.log(`โ
MinIO Provider: Audience validated: ${tokenAudience.join(', ')}`);
}
else {
console.log(`โ ๏ธ MinIO Provider: No audience claim in broker token - skipping audience validation`);
}
// TODO: Validate signature against JWKS if configured
// For now, we'll do basic validation and trust the broker
if (this.config.brokerAuth.jwksUri) {
console.log('๐ MinIO Provider: JWKS validation not yet implemented - trusting broker token');
// In production, implement full JWKS signature validation here
}
console.log('โ
MinIO Provider: Broker token validated successfully');
console.log(` Subject: ${payload.sub || 'not provided'}`);
console.log(` Issuer: ${payload.iss}`);
console.log(` Audience: ${payload.aud ? (Array.isArray(payload.aud) ? payload.aud.join(', ') : payload.aud) : 'none'}`);
}
catch (error) {
console.error('โ MinIO Provider: Broker token validation failed:', error);
throw new Error(`Broker authentication failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
exports.MinIOProvider = MinIOProvider;
//# sourceMappingURL=minio.js.map