feat: Implement Docker registry token endpoint and enhance registry request handling

This commit is contained in:
2025-11-25 19:46:18 +00:00
parent 76793d512b
commit 5cf9c72dd4
4 changed files with 201 additions and 4 deletions

View File

@@ -76,13 +76,13 @@ export class RegistryManager {
},
ociTokens: {
enabled: true,
realm: 'onebox-registry',
realm: 'http://localhost:3000/v2/token',
service: 'onebox-registry',
},
},
oci: {
enabled: true,
basePath: '/v2',
basePath: '', // Empty basePath - OCI paths are passed directly as /v2/...
},
});
@@ -105,7 +105,68 @@ export class RegistryManager {
}
try {
return await this.registry.handleRequest(req);
// Convert native Request to IRequestContext format expected by smartregistry
const url = new URL(req.url);
const headers: Record<string, string> = {};
req.headers.forEach((value, key) => {
headers[key] = value;
});
const query: Record<string, string> = {};
url.searchParams.forEach((value, key) => {
query[key] = value;
});
// Read body for non-GET requests
// IMPORTANT: smartregistry expects Buffer (not Uint8Array) for proper digest calculation
// Buffer.isBuffer(Uint8Array) returns false, causing JSON.stringify which corrupts the data
let body: Buffer | undefined;
if (req.method !== 'GET' && req.method !== 'HEAD') {
const bodyData = await req.arrayBuffer();
if (bodyData.byteLength > 0) {
body = Buffer.from(bodyData);
}
}
// smartregistry v2.0.0 handles JWT tokens natively - no decoding needed
// Pass rawBody for content-addressable operations (manifest push needs exact bytes for digest)
const context = {
method: req.method,
path: url.pathname,
headers,
query,
body,
rawBody: body, // smartregistry uses rawBody for digest calculation
};
const result = await this.registry.handleRequest(context);
// Log the result for debugging
logger.info(`Registry response: status=${result.status}, headers=${JSON.stringify(result.headers)}`);
// smartregistry v2.0.0 now properly includes WWW-Authenticate headers on 401 responses
// Convert IResponse back to native Response
const responseHeaders = new Headers(result.headers || {});
let responseBody: BodyInit | null = null;
if (result.body !== undefined) {
if (result.body instanceof Uint8Array) {
responseBody = result.body;
} else if (typeof result.body === 'string') {
responseBody = result.body;
} else {
responseBody = JSON.stringify(result.body);
if (!responseHeaders.has('Content-Type')) {
responseHeaders.set('Content-Type', 'application/json');
}
}
}
return new Response(responseBody, {
status: result.status,
headers: responseHeaders,
});
} catch (error) {
logger.error(`Registry request error: ${getErrorMessage(error)}`);
return new Response('Internal registry error', { status: 500 });
@@ -195,6 +256,41 @@ export class RegistryManager {
return randomSecret;
}
/**
* Get the auth manager from the registry
*/
getAuthManager(): any {
if (!this.isInitialized) {
throw new Error('Registry not initialized');
}
return this.registry.getAuthManager();
}
/**
* Create an OCI token for Docker authentication
* @param repository - Repository name (e.g., 'hello-world') or '*' for all
* @param actions - Actions to allow: 'push', 'pull', or both
* @param expiresIn - Token expiry in seconds (default: 3600)
*/
async createOciToken(
repository: string = '*',
actions: ('push' | 'pull')[] = ['push', 'pull'],
expiresIn: number = 3600
): Promise<string> {
if (!this.isInitialized) {
throw new Error('Registry not initialized');
}
const authManager = this.registry.getAuthManager();
// Create scopes for the token
const scopes = actions.map(action => `oci:repository:${repository}:${action}`);
// Create OCI token with scopes
const token = await authManager.createOciToken('onebox-system', scopes, expiresIn);
return token;
}
/**
* Get the registry base URL
*/