UNPKG

@voidkey/broker-core

Version:

Core credential minting logic for the voidkey zero-trust credential broker

251 lines โ€ข 12.6 kB
"use strict"; 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