Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions rust/agama-software/src/callbacks/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,30 @@ use agama_utils::{actor::Handler, api::question::QuestionSpec, question};
use i18n_format::i18n_format;
use zypp_agama::callbacks::security;

use crate::callbacks::ask_software_question;
use crate::{callbacks::ask_software_question, state::RepoKey};

#[derive(Clone)]
pub struct Security {
questions: Handler<question::Service>,
trusted_gpg_keys: Vec<RepoKey>,
unsigned_repos: Vec<String>,
}

impl Security {
pub fn new(questions: Handler<question::Service>) -> Self {
Self { questions }
Self {
questions,
trusted_gpg_keys: vec![],
unsigned_repos: vec![],
}
}

pub fn set_trusted_gpg_keys(&mut self, trusted_gpg_keys: Vec<RepoKey>) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NP: why not a public field?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, I consider it as private property that can be set, but read only internally...if I see need to both get/set, then I would change it, but for now I use this set variant as write only. Originally I even have that "with" variant, but it makes code manipulation harder to read.

self.trusted_gpg_keys = trusted_gpg_keys;
}

pub fn set_unsigned_repos(&mut self, unsigned_repos: Vec<String>) {
self.unsigned_repos = unsigned_repos;
}
}

Expand All @@ -23,7 +37,11 @@ impl security::Callback for Security {
file,
repository_alias
);
// TODO: support for extra_repositories with allow_unsigned config

if self.unsigned_repos.contains(&repository_alias) {
return true;
}

// TODO: localization for text when parameters in gextext will be solved
let text = if repository_alias.is_empty() {
format!(
Expand Down Expand Up @@ -53,22 +71,39 @@ impl security::Callback for Security {
key_id: String,
key_name: String,
key_fingerprint: String,
_repository_alias: String,
repository_alias: String,
) -> security::GpgKeyTrust {
tracing::info!(
"accept_key callback: key_id='{}', key_name='{}', key_fingerprint='{}'",
key_id,
key_name,
key_fingerprint,
);
// TODO: support for extra_repositories with specified gpg key checksum

let predefined = self
.trusted_gpg_keys
.iter()
.any(|key| key.alias == repository_alias && key.fingerprint == key_fingerprint);
if predefined {
tracing::info!("GPG key trusted as specified in profile");
return security::GpgKeyTrust::Import;
}

let human_fingerprint = key_fingerprint
.chars()
.collect::<Vec<char>>()
.chunks(4)
.map(|chunk| chunk.iter().collect::<String>())
.collect::<Vec<String>>()
.join(" ");

let text = i18n_format!(
// TRANSLATORS: substituting: key ID, (key name), fingerprint
"The key {0} ({1}) with fingerprint {2} is unknown. \
Do you want to trust this key?",
&key_id,
&key_name,
&key_fingerprint
&human_fingerprint
);
let question = QuestionSpec::new(&text, "software.import_gpg")
.with_action_ids(&[gettext_noop("Trust"), gettext_noop("Skip")])
Expand Down
29 changes: 29 additions & 0 deletions rust/agama-software/src/model/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ use url::Url;

use crate::{model::software_selection::SoftwareSelection, Resolvable, ResolvableType};

#[derive(Clone, Debug)]
pub struct RepoKey {
pub alias: String,
pub fingerprint: String,
}

/// Represents the wanted software configuration.
///
/// It includes the list of repositories, selected resolvables, configuration
Expand All @@ -50,6 +56,8 @@ pub struct SoftwareState {
pub options: SoftwareOptions,
pub registration: Option<RegistrationState>,
pub allow_registration: bool,
pub trusted_gpg_keys: Vec<RepoKey>,
pub unsigned_repos: Vec<String>,
}

impl SoftwareState {
Expand All @@ -62,6 +70,8 @@ impl SoftwareState {
options: Default::default(),
registration: None,
allow_registration: false,
trusted_gpg_keys: vec![],
unsigned_repos: vec![],
}
}
}
Expand Down Expand Up @@ -218,6 +228,23 @@ impl<'a> SoftwareStateBuilder<'a> {
if let Some(repositories) = &config.extra_repositories {
let extra = repositories.iter().map(Repository::from);
state.repositories.extend(extra);

// create map for gpg signatures
for repo in repositories {
if let Some(gpg_fingerprints) = &repo.gpg_fingerprints {
for fingerprint in gpg_fingerprints {
state.trusted_gpg_keys.push(RepoKey {
alias: repo.alias.clone(),
// remove all whitespaces to sanitize input
fingerprint: fingerprint.replace(" ", ""),
});
}
}

if repo.allow_unsigned == Some(true) {
state.unsigned_repos.push(repo.alias.clone());
}
}
}

if let Some(patterns) = &config.patterns {
Expand Down Expand Up @@ -371,6 +398,8 @@ impl<'a> SoftwareStateBuilder<'a> {
registration: None,
options: Default::default(),
allow_registration: self.product.registration,
trusted_gpg_keys: vec![],
unsigned_repos: vec![],
}
}
}
Expand Down
13 changes: 12 additions & 1 deletion rust/agama-software/src/zypp_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use crate::{
registration::RegistrationError,
state::{self, SoftwareState},
},
state::{Addon, RegistrationState, ResolvableSelection},
state::{Addon, RegistrationState, RepoKey, ResolvableSelection},
Registration, ResolvableType,
};

Expand Down Expand Up @@ -127,6 +127,8 @@ pub struct ZyppServer {
registration: RegistrationStatus,
root_dir: Utf8PathBuf,
install_dir: Utf8PathBuf,
trusted_keys: Vec<RepoKey>,
unsigned_repos: Vec<String>,
}

impl ZyppServer {
Expand All @@ -144,6 +146,8 @@ impl ZyppServer {
root_dir: root_dir.as_ref().to_path_buf(),
install_dir: install_dir.as_ref().to_path_buf(),
registration: Default::default(),
trusted_keys: vec![],
unsigned_repos: vec![],
};

// drop the returned JoinHandle: the thread will be detached
Expand Down Expand Up @@ -238,6 +242,8 @@ impl ZyppServer {
callbacks::CommitDownload::new(progress.clone(), question.clone());
let mut install_callback = callbacks::Install::new(progress.clone(), question.clone());
let mut security_callback = callbacks::Security::new(question);
security_callback.set_trusted_gpg_keys(self.trusted_keys.clone());
security_callback.set_unsigned_repos(self.unsigned_repos.clone());

let packages_count = zypp.packages_count();
// use packages count *2 as we need to download package and also install it
Expand Down Expand Up @@ -313,6 +319,11 @@ impl ZyppServer {
self.update_registration(registration_config, &zypp, &security_srv, &mut issues);
}

self.trusted_keys = state.trusted_gpg_keys;
security.set_trusted_gpg_keys(self.trusted_keys.clone());
self.unsigned_repos = state.unsigned_repos;
security.set_unsigned_repos(self.unsigned_repos.clone());

progress.cast(progress::message::Next::new(Scope::Software))?;
let old_aliases: Vec<_> = old_state
.repositories
Expand Down
7 changes: 7 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
-------------------------------------------------------------------
Wed Jan 28 09:59:43 UTC 2026 - Josef Reidinger <jreidinger@suse.com>

- Support "gpgFingerprints" and "allowUnsigned" keys in
"software/extraRepositories" section of profile
(gh#agama-project/agama#3087)

-------------------------------------------------------------------
Tue Jan 27 18:35:59 UTC 2026 - Ladislav Slezák <lslezak@suse.com>

Expand Down
Loading