Skip to content

Commit adfb217

Browse files
authored
RestServer - add image regex to limit the valid image (#107)
remove force acr image and add image regex to limit the valid image
1 parent 4eca0c5 commit adfb217

File tree

5 files changed

+58
-28
lines changed

5 files changed

+58
-28
lines changed

src/rest-server/deploy/rest-server.yaml.template

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ spec:
174174
value: {{ cluster_cfg['rest-server']['hived-computing-device-envs'] }}
175175
- name: ALERT_MANAGER_URL
176176
value: "{{ cluster_cfg['alert-manager']['url'] }}"
177+
- name: IMAGE_REGEX
178+
value: "{{ cluster_cfg['rest-server']['image-regex'] | default("") }}"
177179
ports:
178180
- name: rest-server
179181
containerPort: 8080

src/rest-server/src/config/launcher.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const k8sLauncherConfigSchema = Joi.object()
8484
jobRestrictionGitScriptName: Joi.string().required(),
8585
clstoreHostPath: Joi.string().empty(''),
8686
clstoreJobPath: Joi.string().empty(''),
87+
imageRegex: Joi.string().empty(''),
8788
})
8889
.required();
8990

@@ -156,6 +157,7 @@ if (launcherType === 'k8s') {
156157
jobRestrictionGitScriptName: process.env.JOB_RESTRICTION_GIT_SCRIPT_NAME || 'unset',
157158
clstoreHostPath: process.env.CLUSTER_LOCAL_STORAGE_HOST_PATH,
158159
clstoreJobPath: process.env.CLUSTER_LOCAL_STORAGE_JOB_PATH,
160+
imageRegex: process.env.IMAGE_REGEX || '',
159161
};
160162

161163
const { error, value } = k8sLauncherConfigSchema.validate(launcherConfig);

src/rest-server/src/middlewares/v2/protocol.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const hived = require('@pai/middlewares/v2/hived');
2424
const { enabledHived } = require('@pai/config/launcher');
2525
const protocolSchema = require('@pai/config/v2/protocol');
2626
const asyncHandler = require('@pai/middlewares/v2/asyncHandler');
27+
const logger = require('@pai/config/logger');
28+
const launcherConfig = require('@pai/config/launcher');
2729

2830
const mustacheWriter = new mustache.Writer();
2931

@@ -53,6 +55,19 @@ const render = (template, dict, tags = ['<%', '%>']) => {
5355
return result.trim();
5456
};
5557

58+
const getImageName = (prerequisites) => {
59+
if (
60+
typeof prerequisites !== 'object' ||
61+
typeof prerequisites.dockerimage !== 'object'
62+
) {
63+
return null;
64+
}
65+
66+
const dockerImages = Object.values(prerequisites.dockerimage);
67+
const img = dockerImages.find(p => p.type === 'dockerimage' && p.uri);
68+
return img?.uri ?? null;
69+
};
70+
5671
const protocolValidate = (protocolYAML) => {
5772
const protocolObj = yaml.load(protocolYAML);
5873
if (!protocolSchema.validate(protocolObj)) {
@@ -87,6 +102,42 @@ const protocolValidate = (protocolYAML) => {
87102
prerequisiteSet.add(item.name);
88103
}
89104
}
105+
const imageRegexPattern = launcherConfig.imageRegex;
106+
let imageRegex;
107+
108+
try {
109+
if (imageRegexPattern && imageRegexPattern.length > 0) {
110+
imageRegex = new RegExp(imageRegexPattern);
111+
}
112+
} catch (error) {
113+
logger.info(`Invalid imageRegex pattern: ${imageRegexPattern}. Error: ${error.message}`);
114+
throw createError(
115+
'Internal Server Error',
116+
'InvalidImageRegexError',
117+
`The provided imageRegex pattern "${imageRegexPattern}" is invalid.`
118+
);
119+
}
120+
121+
if (imageRegex) {
122+
const imageName = getImageName(protocolObj.prerequisites);
123+
// Check if the imageName matches the imageRegex
124+
if (!imageName) {
125+
throw createError(
126+
'Bad Request',
127+
'NoDockerImageError',
128+
'No valid docker image found in prerequisites.'
129+
);
130+
}
131+
// Check if the imageName matches the imageRegex
132+
const match = imageRegex.test(imageName);
133+
if (!match) {
134+
throw createError(
135+
'Bad Request',
136+
'InvalidImageError',
137+
`The image ${imageName} is not allowed.`
138+
);
139+
}
140+
}
90141
}
91142
protocolObj.prerequisites = prerequisites;
92143
// convert deployments list to dict

src/rest-server/src/middlewares/v2/quota.js

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,6 @@ const getReqeustedGpuCount = (protocol) => {
7575
return gpuCount;
7676
};
7777

78-
function getImageName(prerequisites) {
79-
if (
80-
typeof prerequisites !== 'object' ||
81-
typeof prerequisites.dockerimage !== 'object'
82-
) {
83-
return null;
84-
}
85-
86-
const dockerImages = Object.values(prerequisites.dockerimage);
87-
const img = dockerImages.find(p => p.type === 'dockerimage' && p.uri);
88-
return img?.uri ?? null;
89-
}
90-
9178
const getJobPriority = (protocol) => {
9279
return protocol.extras?.hivedScheduler?.jobPriorityClass || null;
9380
};
@@ -104,21 +91,6 @@ const check = async (req, res, next) => {
10491
const userPrioritySet = userInfo.extension?.jobPriority ?? null;
10592
const userPriorityExpiration = userInfo.extension?.jobExpiration ?? null;
10693

107-
const imageName = getImageName(jobProtocol.prerequisites);
108-
const imageRepo = imageName.split('/')[0].trim().toLowerCase();
109-
const isAzureCR = /\.azurecr\.io$/i.test(imageRepo);
110-
// Reject if it’s not ACR
111-
if (!isAzureCR) {
112-
return next(
113-
createError(
114-
'Forbidden',
115-
'InvalidImageError',
116-
`The image ${imageRepo || imageName} is not allowed. ` +
117-
`Please use an image from Azure Container Registry (ACR).`
118-
)
119-
);
120-
}
121-
12294
if (jobPriority === 'prod') {
12395
if (userPrioritySet !== 1) {
12496
logger.debug(

src/rest-server/src/utils/error.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ declare type Code =
3333
'ForbiddenUserError' |
3434
'ForbiddenKeyError' |
3535
'IncorrectPasswordError' |
36+
'InvalidImageError' |
37+
'InvalidImageRegexError' |
3638
'InvalidParametersError' |
3739
'NoApiError' |
40+
'NoDockerImageError' |
3841
'NoJobError' |
3942
'NoJobConfigError' |
4043
'NoJobSshInfoError' |

0 commit comments

Comments
 (0)