259 lines
9.6 KiB
JavaScript
259 lines
9.6 KiB
JavaScript
const fs = require('fs');
|
|
const crypto = require('crypto');
|
|
const zlib = require('zlib');
|
|
const { getAuth } = require('./auth_manager');
|
|
|
|
const REPO_NAME = 'soc-gateway';
|
|
|
|
function createTar(files) {
|
|
const chunks = [];
|
|
for (const file of files) {
|
|
const name = file.name;
|
|
const content = Buffer.isBuffer(file.content) ? file.content : Buffer.from(file.content, 'utf8');
|
|
const size = content.length;
|
|
|
|
const header = Buffer.alloc(512);
|
|
header.write(name, 0, 100, 'utf8');
|
|
header.write('0000644\0', 100, 8, 'utf8');
|
|
header.write('0000000\0', 108, 8, 'utf8');
|
|
header.write('0000000\0', 116, 8, 'utf8');
|
|
header.write(size.toString(8).padStart(11, '0') + '\0', 124, 12, 'utf8');
|
|
const mtime = Math.floor(Date.now() / 1000);
|
|
header.write(mtime.toString(8).padStart(11, '0') + '\0', 136, 12, 'utf8');
|
|
header.write(' ', 148, 8, 'utf8');
|
|
header.write('0', 156, 1, 'utf8');
|
|
header.write('ustar\0', 257, 6, 'utf8');
|
|
header.write('00', 263, 2, 'utf8');
|
|
|
|
let sum = 0;
|
|
for (let i = 0; i < 512; i++) sum += header[i];
|
|
header.write(sum.toString(8).padStart(6, '0') + '\0 ', 148, 8, 'utf8');
|
|
|
|
chunks.push(header);
|
|
chunks.push(content);
|
|
|
|
const remainder = size % 512;
|
|
if (remainder > 0) chunks.push(Buffer.alloc(512 - remainder));
|
|
}
|
|
chunks.push(Buffer.alloc(1024));
|
|
return Buffer.concat(chunks);
|
|
}
|
|
|
|
async function uploadBlobToGhaymah(blobBuffer, digest, userId) {
|
|
let authData = await getAuth();
|
|
let auth = Buffer.from(userId + ':' + authData.token).toString('base64');
|
|
|
|
// Check if blob already exists
|
|
let checkRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/blobs/${digest}`, {
|
|
method: 'HEAD',
|
|
headers: { 'Authorization': 'Basic ' + auth }
|
|
});
|
|
if (checkRes.status === 401) {
|
|
authData = await getAuth(true);
|
|
auth = Buffer.from(userId + ':' + authData.token).toString('base64');
|
|
checkRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/blobs/${digest}`, {
|
|
method: 'HEAD',
|
|
headers: { 'Authorization': 'Basic ' + auth }
|
|
});
|
|
}
|
|
if (checkRes.ok) {
|
|
console.log(`Blob ${digest.substring(0, 19)} already exists on Ghaymah!`);
|
|
return;
|
|
}
|
|
|
|
// Initiate upload
|
|
let initRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/blobs/uploads/`, {
|
|
method: 'POST',
|
|
headers: { 'Authorization': 'Basic ' + auth }
|
|
});
|
|
if (initRes.status === 401) {
|
|
authData = await getAuth(true);
|
|
auth = Buffer.from(userId + ':' + authData.token).toString('base64');
|
|
initRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/blobs/uploads/`, {
|
|
method: 'POST',
|
|
headers: { 'Authorization': 'Basic ' + auth }
|
|
});
|
|
}
|
|
if (initRes.status !== 202) {
|
|
throw new Error(`Failed to initiate blob upload: ${initRes.status} ${await initRes.text()}`);
|
|
}
|
|
let uploadUrl = initRes.headers.get('location');
|
|
if (uploadUrl.startsWith('/')) {
|
|
uploadUrl = 'https://registry.ghaymah.systems' + uploadUrl;
|
|
}
|
|
const separator = uploadUrl.includes('?') ? '&' : '?';
|
|
const putUrl = `${uploadUrl}${separator}digest=${digest}`;
|
|
|
|
const putRes = await fetch(putUrl, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': 'Basic ' + auth,
|
|
'Content-Type': 'application/octet-stream',
|
|
'Content-Length': blobBuffer.length.toString()
|
|
},
|
|
body: blobBuffer
|
|
});
|
|
if (putRes.status !== 201) {
|
|
throw new Error(`Failed to put blob ${digest}: ${putRes.status} ${await putRes.text()}`);
|
|
}
|
|
console.log(`Uploaded blob ${digest.substring(0, 19)} (${blobBuffer.length} bytes)`);
|
|
}
|
|
|
|
async function main() {
|
|
const { token, userId } = await getAuth();
|
|
const auth = Buffer.from(userId + ':' + token).toString('base64');
|
|
|
|
console.log('=== Step 1: Getting Docker Hub Token for nginx:alpine ===');
|
|
const dAuth = await (await fetch('https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/nginx:pull')).json();
|
|
const dToken = dAuth.token;
|
|
|
|
console.log('=== Step 2: Fetching linux/amd64 Manifest ===');
|
|
const manListRes = await fetch('https://registry-1.docker.io/v2/library/nginx/manifests/alpine', {
|
|
headers: {
|
|
'Authorization': 'Bearer ' + dToken,
|
|
'Accept': 'application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json'
|
|
}
|
|
});
|
|
const manList = await manListRes.json();
|
|
const amd64 = manList.manifests?.find(m => m.platform?.architecture === 'amd64' && m.platform?.os === 'linux');
|
|
const manifestDigest = amd64 ? amd64.digest : 'alpine';
|
|
|
|
const manifestRes = await fetch(`https://registry-1.docker.io/v2/library/nginx/manifests/${manifestDigest}`, {
|
|
headers: {
|
|
'Authorization': 'Bearer ' + dToken,
|
|
'Accept': 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json'
|
|
}
|
|
});
|
|
const manifest = await manifestRes.json();
|
|
console.log(`Base image has ${manifest.layers.length} layers, config: ${manifest.config.digest}`);
|
|
|
|
console.log('\n=== Step 3: Copying Base Layers from Docker Hub to Ghaymah Registry ===');
|
|
for (let i = 0; i < manifest.layers.length; i++) {
|
|
const layer = manifest.layers[i];
|
|
console.log(`[Layer ${i + 1}/${manifest.layers.length}] Fetching ${layer.digest.substring(0, 19)}...`);
|
|
const layerRes = await fetch(`https://registry-1.docker.io/v2/library/nginx/blobs/${layer.digest}`, {
|
|
headers: { 'Authorization': 'Bearer ' + dToken }
|
|
});
|
|
const layerBuf = Buffer.from(await layerRes.arrayBuffer());
|
|
await uploadBlobToGhaymah(layerBuf, layer.digest, userId);
|
|
}
|
|
|
|
console.log('\n=== Step 4: Fetching Base Config ===');
|
|
const configRes = await fetch(`https://registry-1.docker.io/v2/library/nginx/blobs/${manifest.config.digest}`, {
|
|
headers: { 'Authorization': 'Bearer ' + dToken }
|
|
});
|
|
const baseConfig = await configRes.json();
|
|
|
|
console.log('\n=== Step 5: Packaging SOC Command Center Files ===');
|
|
const htmlContent = fs.readFileSync('D:/cumin/ghaymah_dns_net_sec_tests/soc_gateway_ui/index.html', 'utf8');
|
|
const nginxConf = `server {
|
|
listen 80;
|
|
server_name _;
|
|
|
|
location / {
|
|
root /usr/share/nginx/html;
|
|
index index.html;
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
|
|
location /proxy/ {
|
|
proxy_pass http://soc-backend-d1895c70f6b2:4000/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_read_timeout 10s;
|
|
}
|
|
}
|
|
`;
|
|
|
|
// Create uncompressed tar
|
|
const tarBuf = createTar([
|
|
{ name: 'usr/share/nginx/html/index.html', content: htmlContent },
|
|
{ name: 'etc/nginx/conf.d/default.conf', content: nginxConf }
|
|
]);
|
|
const diffId = 'sha256:' + crypto.createHash('sha256').update(tarBuf).digest('hex');
|
|
|
|
// Gzip compress
|
|
const gzipBuf = zlib.gzipSync(tarBuf);
|
|
const customLayerDigest = 'sha256:' + crypto.createHash('sha256').update(gzipBuf).digest('hex');
|
|
const customLayerSize = gzipBuf.length;
|
|
|
|
console.log(`Custom layer: ${customLayerDigest} (size: ${customLayerSize}, diffId: ${diffId})`);
|
|
await uploadBlobToGhaymah(gzipBuf, customLayerDigest, userId);
|
|
|
|
console.log('\n=== Step 6: Creating and Uploading Custom Image Config ===');
|
|
const newConfig = { ...baseConfig };
|
|
newConfig.rootfs = {
|
|
type: 'layers',
|
|
diff_ids: [...(baseConfig.rootfs?.diff_ids || []), diffId]
|
|
};
|
|
newConfig.history = [
|
|
...(baseConfig.history || []),
|
|
{
|
|
created: new Date().toISOString(),
|
|
created_by: 'ADD soc_gateway_ui /usr/share/nginx/html'
|
|
}
|
|
];
|
|
|
|
const newConfigBuf = Buffer.from(JSON.stringify(newConfig));
|
|
const newConfigDigest = 'sha256:' + crypto.createHash('sha256').update(newConfigBuf).digest('hex');
|
|
console.log(`New config digest: ${newConfigDigest}`);
|
|
await uploadBlobToGhaymah(newConfigBuf, newConfigDigest, userId);
|
|
|
|
console.log('\n=== Step 7: Publishing Image Manifest as latest ===');
|
|
const newManifest = {
|
|
schemaVersion: 2,
|
|
mediaType: 'application/vnd.docker.distribution.manifest.v2+json',
|
|
config: {
|
|
mediaType: 'application/vnd.docker.container.image.v1+json',
|
|
size: newConfigBuf.length,
|
|
digest: newConfigDigest
|
|
},
|
|
layers: [
|
|
...manifest.layers.map(l => ({
|
|
mediaType: 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
|
size: l.size,
|
|
digest: l.digest
|
|
})),
|
|
{
|
|
mediaType: 'application/vnd.docker.image.rootfs.diff.tar.gzip',
|
|
size: customLayerSize,
|
|
digest: customLayerDigest
|
|
}
|
|
]
|
|
};
|
|
|
|
let curAuth = await getAuth();
|
|
let putAuth = Buffer.from(userId + ':' + curAuth.token).toString('base64');
|
|
let putManRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/manifests/latest`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': 'Basic ' + putAuth,
|
|
'Content-Type': 'application/vnd.docker.distribution.manifest.v2+json'
|
|
},
|
|
body: JSON.stringify(newManifest)
|
|
});
|
|
if (putManRes.status === 401) {
|
|
curAuth = await getAuth(true);
|
|
putAuth = Buffer.from(userId + ':' + curAuth.token).toString('base64');
|
|
putManRes = await fetch(`https://registry.ghaymah.systems/v2/${userId}/${REPO_NAME}/manifests/latest`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': 'Basic ' + putAuth,
|
|
'Content-Type': 'application/vnd.docker.distribution.manifest.v2+json'
|
|
},
|
|
body: JSON.stringify(newManifest)
|
|
});
|
|
}
|
|
|
|
console.log('Manifest PUT Status:', putManRes.status, putManRes.statusText);
|
|
if (!putManRes.ok) {
|
|
throw new Error('Manifest upload failed: ' + (await putManRes.text()));
|
|
}
|
|
|
|
console.log('\n>>> SUCCESS! DOCKER IMAGE SUCCESSFULLY PUSHED TO REGISTRY.GHAYMAH.SYSTEMS! <<<');
|
|
console.log(`Image: registry.ghaymah.systems/${userId}/${REPO_NAME}:latest`);
|
|
}
|
|
|
|
main().catch(console.error);
|