mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-04-21 17:47:22 +00:00
feat: add ability to add insecure registry / skipping cert verify (#2896)
* draft: add ability to add insecure registry / skipping cert verify ### What does this PR do? Adds the ability to prompt the user that the certificate is unverifiable but they can add it / skip the verification process if they wish. ### Screenshot/screencast of this PR <!-- Please include a screenshot or a screencast explaining what is doing this PR --> ### What issues does this PR fix or reference? <!-- Please include any related issue from Podman Desktop repository (or from another issue tracker). --> ### How to test this PR? <!-- Please explain steps to reproduce --> 1. Add a test registry (registry.k8s.land) which has a self-signed certificate 2. Podman Desktop should prompt that it is unverifiable / cert does not work 3. PD should succesfully add the registry Signed-off-by: Charlie Drage <charlie@charliedrage.com> * renaming and refactor getOptions Signed-off-by: Charlie Drage <charlie@charliedrage.com> --------- Signed-off-by: Charlie Drage <charlie@charliedrage.com>
This commit is contained in:
parent
fe18540ff9
commit
afcc91effc
7 changed files with 79 additions and 8 deletions
0
kind
Executable file
0
kind
Executable file
|
|
@ -445,6 +445,7 @@ declare module '@podman-desktop/api' {
|
|||
serverUrl: string;
|
||||
username: string;
|
||||
secret: string;
|
||||
insecure?: boolean;
|
||||
}
|
||||
|
||||
export interface RegistryProvider {
|
||||
|
|
|
|||
|
|
@ -385,6 +385,21 @@ describe('expect checkCredentials', async () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('expect checkCredentials works with ignoring the certificate', async () => {
|
||||
const spyGetAuthInfo = vi.spyOn(imageRegistry, 'getAuthInfo');
|
||||
spyGetAuthInfo.mockResolvedValue({ authUrl: 'foo', scheme: 'bearer' });
|
||||
|
||||
const spydoCheckCredentials = vi.spyOn(imageRegistry, 'doCheckCredentials');
|
||||
spydoCheckCredentials.mockResolvedValue();
|
||||
|
||||
await imageRegistry.checkCredentials(
|
||||
'my-podman-desktop-fake-registry.io/my/extension',
|
||||
'my-username',
|
||||
'my-password',
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('expect checkCredentials fails', async () => {
|
||||
const spyGetAuthInfo = vi.spyOn(imageRegistry, 'getAuthInfo');
|
||||
spyGetAuthInfo.mockResolvedValue({ authUrl: 'foo', scheme: 'bearer' });
|
||||
|
|
|
|||
|
|
@ -242,10 +242,12 @@ export class ImageRegistry {
|
|||
if (exists) {
|
||||
throw new Error(`Registry ${registryCreateOptions.serverUrl} already exists`);
|
||||
}
|
||||
|
||||
await this.checkCredentials(
|
||||
registryCreateOptions.serverUrl,
|
||||
registryCreateOptions.username,
|
||||
registryCreateOptions.secret,
|
||||
registryCreateOptions.insecure,
|
||||
);
|
||||
const registry = provider.create(registryCreateOptions);
|
||||
return this.registerRegistry(registry);
|
||||
|
|
@ -308,7 +310,7 @@ export class ImageRegistry {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
getOptions(): OptionsOfTextResponseBody {
|
||||
getOptions(insecure?: boolean): OptionsOfTextResponseBody {
|
||||
const httpsOptions: HttpsOptions = {};
|
||||
const options: OptionsOfTextResponseBody = {
|
||||
https: httpsOptions,
|
||||
|
|
@ -316,6 +318,9 @@ export class ImageRegistry {
|
|||
|
||||
if (options.https) {
|
||||
options.https.certificateAuthority = this.certificates.getAllCertificates();
|
||||
if (insecure) {
|
||||
options.https.rejectUnauthorized = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.proxyEnabled) {
|
||||
|
|
@ -650,8 +655,9 @@ export class ImageRegistry {
|
|||
return this.getManifestFromURL(manifestURL, imageData, token);
|
||||
}
|
||||
|
||||
async getAuthInfo(serviceUrl: string): Promise<{ authUrl: string; scheme: string }> {
|
||||
async getAuthInfo(serviceUrl: string, insecure?: boolean): Promise<{ authUrl: string; scheme: string }> {
|
||||
let registryUrl: string;
|
||||
const options = this.getOptions(insecure);
|
||||
|
||||
if (serviceUrl.includes('docker.io')) {
|
||||
registryUrl = 'https://index.docker.io/v2/';
|
||||
|
|
@ -667,7 +673,7 @@ export class ImageRegistry {
|
|||
let scheme = '';
|
||||
|
||||
try {
|
||||
await got.get(registryUrl, this.getOptions());
|
||||
await got.get(registryUrl, options);
|
||||
} catch (requestErr) {
|
||||
if (requestErr instanceof HTTPError) {
|
||||
const wwwAuthenticate = requestErr.response?.headers['www-authenticate'];
|
||||
|
|
@ -703,7 +709,7 @@ export class ImageRegistry {
|
|||
return { authUrl, scheme };
|
||||
}
|
||||
|
||||
async checkCredentials(serviceUrl: string, username: string, password: string): Promise<void> {
|
||||
async checkCredentials(serviceUrl: string, username: string, password: string, insecure?: boolean): Promise<void> {
|
||||
if (serviceUrl === undefined || !validator.isURL(serviceUrl)) {
|
||||
throw Error(
|
||||
'The format of the Registry Location is incorrect.\nPlease use the format "registry.location.com" and try again.',
|
||||
|
|
@ -718,10 +724,10 @@ export class ImageRegistry {
|
|||
throw Error('Password should not be empty.');
|
||||
}
|
||||
|
||||
const { authUrl, scheme } = await this.getAuthInfo(serviceUrl);
|
||||
const { authUrl, scheme } = await this.getAuthInfo(serviceUrl, insecure);
|
||||
|
||||
if (authUrl !== undefined) {
|
||||
await this.doCheckCredentials(scheme, authUrl, username, password);
|
||||
await this.doCheckCredentials(scheme, authUrl, username, password, insecure);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -758,12 +764,19 @@ export class ImageRegistry {
|
|||
return response.token;
|
||||
}
|
||||
|
||||
async doCheckCredentials(scheme: string, authUrl: string, username: string, password: string): Promise<void> {
|
||||
async doCheckCredentials(
|
||||
scheme: string,
|
||||
authUrl: string,
|
||||
username: string,
|
||||
password: string,
|
||||
insecure?: boolean,
|
||||
): Promise<void> {
|
||||
const options = this.getOptions(insecure);
|
||||
|
||||
let rawResponse: string | undefined;
|
||||
// add credentials in the header
|
||||
// encode username:password in base64
|
||||
const token = Buffer.from(`${username}:${password}`).toString('base64');
|
||||
const options = this.getOptions();
|
||||
options.headers = {
|
||||
Authorization: `Basic ${token}`,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1195,6 +1195,18 @@ export class PluginSystem {
|
|||
},
|
||||
);
|
||||
|
||||
// Check credentials for a registry
|
||||
this.ipcHandle(
|
||||
'image-registry:checkCredentials',
|
||||
async (_listener, registryCreateOptions: containerDesktopAPI.RegistryCreateOptions): Promise<void> => {
|
||||
return imageRegistry.checkCredentials(
|
||||
registryCreateOptions.serverUrl,
|
||||
registryCreateOptions.username,
|
||||
registryCreateOptions.secret,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
this.ipcHandle(
|
||||
'image-registry:createRegistry',
|
||||
async (
|
||||
|
|
|
|||
|
|
@ -821,6 +821,13 @@ function initExposure(): void {
|
|||
},
|
||||
);
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
'checkImageCredentials',
|
||||
async (registryCreateOptions: containerDesktopAPI.RegistryCreateOptions): Promise<void> => {
|
||||
return ipcInvoke('image-registry:checkCredentials', registryCreateOptions);
|
||||
},
|
||||
);
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
'updateImageRegistry',
|
||||
async (registry: containerDesktopAPI.Registry): Promise<void> => {
|
||||
|
|
|
|||
|
|
@ -182,6 +182,29 @@ async function loginToRegistry(registry: containerDesktopAPI.Registry) {
|
|||
|
||||
const newRegistry = registry === newRegistryRequest;
|
||||
|
||||
// Always check credentials before creating image / updating to see if they pass.
|
||||
// if we happen to get a certificate verification issue, as the user if they would like to
|
||||
// continue with the registry anyway.
|
||||
try {
|
||||
await window.checkImageCredentials(registry);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes('unable to verify the first certificate')) {
|
||||
const result = await window.showMessageBox({
|
||||
title: 'Invalid Certificate',
|
||||
type: 'warning',
|
||||
message: 'The certificate for this registry is not trusted / verifiable. Would you like to still add it?',
|
||||
buttons: ['Yes', 'No'],
|
||||
});
|
||||
if (result && result.response === 0) {
|
||||
registry.insecure = true;
|
||||
} else {
|
||||
setErrorResponse(registry.serverUrl, error.message);
|
||||
loggingIn = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (newRegistry) {
|
||||
await window.createImageRegistry(registry.source, registry);
|
||||
|
|
|
|||
Loading…
Reference in a new issue